diff --git a/capnp/src/data_list.rs b/capnp/src/data_list.rs index 9822ebbfe..e1d215e07 100644 --- a/capnp/src/data_list.rs +++ b/capnp/src/data_list.rs @@ -215,6 +215,17 @@ impl<'a> From> for crate::dynamic_value::Reader<'a> { } } +impl<'a> crate::dynamic_value::DowncastReader<'a> for Reader<'a> { + fn downcast_reader(v: crate::dynamic_value::Reader<'a>) -> Self { + let dl: crate::dynamic_list::Reader = v.downcast(); + assert_eq!( + dl.element_type(), + crate::introspect::TypeVariant::Data.into() + ); + Reader { reader: dl.reader } + } +} + impl<'a> From> for crate::dynamic_value::Builder<'a> { fn from(t: Builder<'a>) -> crate::dynamic_value::Builder<'a> { crate::dynamic_value::Builder::List(crate::dynamic_list::Builder { @@ -224,6 +235,19 @@ impl<'a> From> for crate::dynamic_value::Builder<'a> { } } +impl<'a> crate::dynamic_value::DowncastBuilder<'a> for Builder<'a> { + fn downcast_builder(v: crate::dynamic_value::Builder<'a>) -> Self { + let dl: crate::dynamic_list::Builder = v.downcast(); + assert_eq!( + dl.element_type(), + crate::introspect::TypeVariant::Data.into() + ); + Builder { + builder: dl.builder, + } + } +} + impl<'a> core::fmt::Debug for Reader<'a> { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { core::fmt::Debug::fmt( diff --git a/capnp/src/dynamic_struct.rs b/capnp/src/dynamic_struct.rs index 2a06c8ad5..aecafc010 100644 --- a/capnp/src/dynamic_struct.rs +++ b/capnp/src/dynamic_struct.rs @@ -26,7 +26,7 @@ pub(crate) fn struct_size_from_schema(schema: StructSchema) -> Result { pub(crate) reader: layout::StructReader<'a>, - schema: StructSchema, + pub(crate) schema: StructSchema, } impl<'a> From> for dynamic_value::Reader<'a> { @@ -232,12 +232,24 @@ impl<'a> Reader<'a> { let field = self.schema.get_field_by_name(field_name)?; self.has(field) } + + /// Downcasts the `Reader` into a specific struct type. Panics if the + /// expected type does not match the value. + pub fn downcast(self) -> T::Reader<'a> { + assert!( + Into::::into(crate::introspect::TypeVariant::Struct( + self.schema.raw + )) + .may_downcast_to(T::introspect()) + ); + self.reader.into() + } } /// A mutable dynamically-typed struct. pub struct Builder<'a> { - builder: layout::StructBuilder<'a>, - schema: StructSchema, + pub(crate) builder: layout::StructBuilder<'a>, + pub(crate) schema: StructSchema, } impl<'a> From> for dynamic_value::Builder<'a> { @@ -773,6 +785,18 @@ impl<'a> Builder<'a> { } Ok(()) } + + /// Downcasts the `Builder` into a specific struct type. Panics if the + /// expected type does not match the value. + pub fn downcast(self) -> T::Builder<'a> { + assert!( + Into::::into(crate::introspect::TypeVariant::Struct( + self.schema.raw + )) + .may_downcast_to(T::introspect()) + ); + self.builder.into() + } } impl<'a> crate::traits::SetterInput for Reader<'a> { diff --git a/capnp/src/dynamic_value.rs b/capnp/src/dynamic_value.rs index a0eecb447..3a049edf6 100644 --- a/capnp/src/dynamic_value.rs +++ b/capnp/src/dynamic_value.rs @@ -68,6 +68,18 @@ impl<'a> Reader<'a> { pub fn downcast>(self) -> T { T::downcast_reader(self) } + + /// Downcasts the `Reader` into a specific struct type. Panics if the + /// expected type does not match the value. + /// + /// Design note: instead of this method, it would be better to add a blanket impl + /// of the `DowncastBuilder` trait that covered every struct type. Unfortunately, + /// the current way the `Introspect` and `OwnedStruct` traits are set up does not + /// seem to allow this. + pub fn downcast_struct(self) -> T::Reader<'a> { + let sr: dynamic_struct::Reader = self.downcast(); + sr.downcast::() + } } impl<'a> From<()> for Reader<'a> { @@ -215,6 +227,18 @@ impl<'a> Builder<'a> { pub fn downcast>(self) -> T { T::downcast_builder(self) } + + /// Downcasts the `Builder` into a specific struct type. Panics if the + /// expected type does not match the value. + /// + /// Design note: instead of this method, it would be better to add a blanket impl + /// of the `DowncastBuilder` trait that covered every struct type. Unfortunately, + /// the current way the `Introspect` and `OwnedStruct` traits are set up does not + /// seem to allow this. + pub fn downcast_struct(self) -> T::Builder<'a> { + let sb: dynamic_struct::Builder = self.downcast(); + sb.downcast::() + } } /// Helper trait for the `dynamic_value::Builder::downcast()` method. diff --git a/capnp/src/enum_list.rs b/capnp/src/enum_list.rs index fbdfc0c03..646c48a72 100644 --- a/capnp/src/enum_list.rs +++ b/capnp/src/enum_list.rs @@ -255,6 +255,19 @@ impl<'a, T: TryFrom + crate::introspect::Introspect> F } } +impl<'a, T: TryFrom + crate::introspect::Introspect> + crate::dynamic_value::DowncastReader<'a> for Reader<'a, T> +{ + fn downcast_reader(v: crate::dynamic_value::Reader<'a>) -> Self { + let dl: crate::dynamic_list::Reader = v.downcast(); + assert!(dl.element_type().may_downcast_to(T::introspect())); + Reader { + reader: dl.reader, + marker: PhantomData, + } + } +} + impl<'a, T: TryFrom + crate::introspect::Introspect> From> for crate::dynamic_value::Builder<'a> { @@ -266,6 +279,19 @@ impl<'a, T: TryFrom + crate::introspect::Introspect> F } } +impl<'a, T: TryFrom + crate::introspect::Introspect> + crate::dynamic_value::DowncastBuilder<'a> for Builder<'a, T> +{ + fn downcast_builder(v: crate::dynamic_value::Builder<'a>) -> Self { + let dl: crate::dynamic_list::Builder = v.downcast(); + assert!(dl.element_type().may_downcast_to(T::introspect())); + Builder { + builder: dl.builder, + marker: PhantomData, + } + } +} + impl<'a, T: Copy + TryFrom + crate::introspect::Introspect> core::fmt::Debug for Reader<'a, T> { diff --git a/capnp/src/introspect.rs b/capnp/src/introspect.rs index 40c3e75e1..6208c81b5 100644 --- a/capnp/src/introspect.rs +++ b/capnp/src/introspect.rs @@ -13,7 +13,7 @@ pub trait Introspect { /// optimized to avoid heap allocation. /// /// To examine a `Type`, you should call the `which()` method. -#[derive(Copy, Clone, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct Type { /// The type, minus any outer `List( )`. base: BaseType, @@ -104,6 +104,42 @@ impl Type { ) } } + + /// Returns true is a dynamic value of type `self` is + /// allowed to be downcast to type `other`. + pub(crate) fn may_downcast_to(&self, other: Self) -> bool { + match (self.which(), other.which()) { + (TypeVariant::Void, TypeVariant::Void) => true, + (TypeVariant::UInt8, TypeVariant::UInt8) => true, + (TypeVariant::UInt16, TypeVariant::UInt16) => true, + (TypeVariant::UInt32, TypeVariant::UInt32) => true, + (TypeVariant::UInt64, TypeVariant::UInt64) => true, + (TypeVariant::Int8, TypeVariant::Int8) => true, + (TypeVariant::Int16, TypeVariant::Int16) => true, + (TypeVariant::Int32, TypeVariant::Int32) => true, + (TypeVariant::Int64, TypeVariant::Int64) => true, + (TypeVariant::Float32, TypeVariant::Float32) => true, + (TypeVariant::Float64, TypeVariant::Float64) => true, + (TypeVariant::Text, TypeVariant::Text) => true, + (TypeVariant::Data, TypeVariant::Data) => true, + (TypeVariant::Enum(es1), TypeVariant::Enum(es2)) => es1 == es2, + (TypeVariant::Struct(rbs1), TypeVariant::Struct(rbs2)) => { + // Ignore any type parameters. The original intent was that + // we would additionally check that the `field_types` fields + // were equal function pointers here. However, according to + // Miri's behavior at least, that check returns `false` + // more than we would like it to. So we settle for being + // a bit more accepting. + core::ptr::eq(rbs1.generic, rbs2.generic) + } + (TypeVariant::List(element1), TypeVariant::List(element2)) => { + element1.may_downcast_to(element2) + } + (TypeVariant::AnyPointer, TypeVariant::AnyPointer) => true, + (TypeVariant::Capability, TypeVariant::Capability) => true, + _ => false, + } + } } #[derive(Copy, Clone, PartialEq, Eq)] diff --git a/capnp/src/list_list.rs b/capnp/src/list_list.rs index f57666898..5667d3412 100644 --- a/capnp/src/list_list.rs +++ b/capnp/src/list_list.rs @@ -284,6 +284,17 @@ impl<'a, T: crate::traits::Owned> From> for crate::dynamic_value:: } } +impl<'a, T: crate::traits::Owned> crate::dynamic_value::DowncastReader<'a> for Reader<'a, T> { + fn downcast_reader(v: crate::dynamic_value::Reader<'a>) -> Self { + let dl: crate::dynamic_list::Reader = v.downcast(); + assert!(dl.element_type().may_downcast_to(T::introspect())); + Reader { + reader: dl.reader, + marker: core::marker::PhantomData, + } + } +} + impl<'a, T: crate::traits::Owned> From> for crate::dynamic_value::Builder<'a> { fn from(t: Builder<'a, T>) -> crate::dynamic_value::Builder<'a> { crate::dynamic_value::Builder::List(crate::dynamic_list::Builder::new( @@ -293,6 +304,17 @@ impl<'a, T: crate::traits::Owned> From> for crate::dynamic_value: } } +impl<'a, T: crate::traits::Owned> crate::dynamic_value::DowncastBuilder<'a> for Builder<'a, T> { + fn downcast_builder(v: crate::dynamic_value::Builder<'a>) -> Self { + let dl: crate::dynamic_list::Builder = v.downcast(); + assert!(dl.element_type().may_downcast_to(T::introspect())); + Builder { + builder: dl.builder, + marker: core::marker::PhantomData, + } + } +} + impl<'a, T: crate::traits::Owned> core::fmt::Debug for Reader<'a, T> { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { core::fmt::Debug::fmt( diff --git a/capnp/src/primitive_list.rs b/capnp/src/primitive_list.rs index 56f378a36..feabb9d0b 100644 --- a/capnp/src/primitive_list.rs +++ b/capnp/src/primitive_list.rs @@ -344,6 +344,19 @@ impl<'a, T: PrimitiveElement + crate::introspect::Introspect> From } } +impl<'a, T: PrimitiveElement + crate::introspect::Introspect> + crate::dynamic_value::DowncastReader<'a> for Reader<'a, T> +{ + fn downcast_reader(v: crate::dynamic_value::Reader<'a>) -> Self { + let dl: crate::dynamic_list::Reader = v.downcast(); + assert!(dl.element_type().may_downcast_to(T::introspect())); + Reader { + reader: dl.reader, + marker: marker::PhantomData, + } + } +} + impl<'a, T: PrimitiveElement + crate::introspect::Introspect> From> for crate::dynamic_value::Builder<'a> { @@ -355,6 +368,19 @@ impl<'a, T: PrimitiveElement + crate::introspect::Introspect> From + crate::dynamic_value::DowncastBuilder<'a> for Builder<'a, T> +{ + fn downcast_builder(v: crate::dynamic_value::Builder<'a>) -> Self { + let dl: crate::dynamic_list::Builder = v.downcast(); + assert!(dl.element_type().may_downcast_to(T::introspect())); + Builder { + builder: dl.builder, + marker: marker::PhantomData, + } + } +} + impl<'a, T: Copy + PrimitiveElement + crate::introspect::Introspect> core::fmt::Debug for Reader<'a, T> { diff --git a/capnp/src/struct_list.rs b/capnp/src/struct_list.rs index b3c45ac45..55aba5d29 100644 --- a/capnp/src/struct_list.rs +++ b/capnp/src/struct_list.rs @@ -291,6 +291,17 @@ impl<'a, T: crate::traits::OwnedStruct> From> for crate::dynamic_v } } +impl<'a, T: crate::traits::OwnedStruct> crate::dynamic_value::DowncastReader<'a> for Reader<'a, T> { + fn downcast_reader(v: crate::dynamic_value::Reader<'a>) -> Self { + let dl: crate::dynamic_list::Reader = v.downcast(); + assert!(dl.element_type().may_downcast_to(T::introspect())); + Reader { + reader: dl.reader, + marker: PhantomData, + } + } +} + impl<'a, T: crate::traits::OwnedStruct> From> for crate::dynamic_value::Builder<'a> { fn from(t: Builder<'a, T>) -> crate::dynamic_value::Builder<'a> { crate::dynamic_value::Builder::List(crate::dynamic_list::Builder::new( @@ -300,6 +311,19 @@ impl<'a, T: crate::traits::OwnedStruct> From> for crate::dynamic_ } } +impl<'a, T: crate::traits::OwnedStruct> crate::dynamic_value::DowncastBuilder<'a> + for Builder<'a, T> +{ + fn downcast_builder(v: crate::dynamic_value::Builder<'a>) -> Self { + let dl: crate::dynamic_list::Builder = v.downcast(); + assert!(dl.element_type().may_downcast_to(T::introspect())); + Builder { + builder: dl.builder, + marker: PhantomData, + } + } +} + impl<'a, T: crate::traits::OwnedStruct> core::fmt::Debug for Reader<'a, T> { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { core::fmt::Debug::fmt( diff --git a/capnp/src/text_list.rs b/capnp/src/text_list.rs index db3a78fa2..905012a29 100644 --- a/capnp/src/text_list.rs +++ b/capnp/src/text_list.rs @@ -250,6 +250,17 @@ impl<'a> From> for crate::dynamic_value::Reader<'a> { } } +impl<'a> crate::dynamic_value::DowncastReader<'a> for Reader<'a> { + fn downcast_reader(v: crate::dynamic_value::Reader<'a>) -> Self { + let dl: crate::dynamic_list::Reader = v.downcast(); + assert_eq!( + dl.element_type(), + crate::introspect::TypeVariant::Text.into() + ); + Reader { reader: dl.reader } + } +} + impl<'a> From> for crate::dynamic_value::Builder<'a> { fn from(t: Builder<'a>) -> crate::dynamic_value::Builder<'a> { crate::dynamic_value::Builder::List(crate::dynamic_list::Builder { @@ -259,6 +270,19 @@ impl<'a> From> for crate::dynamic_value::Builder<'a> { } } +impl<'a> crate::dynamic_value::DowncastBuilder<'a> for Builder<'a> { + fn downcast_builder(v: crate::dynamic_value::Builder<'a>) -> Self { + let dl: crate::dynamic_list::Builder = v.downcast(); + assert_eq!( + dl.element_type(), + crate::introspect::TypeVariant::Text.into() + ); + Builder { + builder: dl.builder, + } + } +} + impl<'a> core::fmt::Debug for Reader<'a> { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { core::fmt::Debug::fmt( diff --git a/capnpc/test/dynamic.rs b/capnpc/test/dynamic.rs index 8ee4babf1..3c27c9b45 100644 --- a/capnpc/test/dynamic.rs +++ b/capnpc/test/dynamic.rs @@ -408,3 +408,101 @@ fn test_get_named_missing() { assert!(root.get_named("zzzzzzz").is_err()); assert!(root.has_named("zzzzzzz").is_err()); } + +#[test] +fn test_downcasts() { + let mut builder = message::Builder::new_default(); + let root: test_all_types::Builder<'_> = builder.init_root(); + let mut root: dynamic_value::Builder<'_> = root.into(); + + test_util::dynamic_init_test_message(root.reborrow().downcast()); + + { + let root_typed = root.reborrow().downcast_struct::(); + assert_eq!(root_typed.get_int16_field(), -12345); + + let root_typed_reader = root + .reborrow() + .into_reader() + .downcast_struct::(); + assert_eq!(root_typed_reader.get_int16_field(), -12345); + } + let mut root_struct: dynamic_struct::Builder<'_> = root.reborrow().downcast(); + { + let int8_list: capnp::primitive_list::Builder<'_, i8> = root_struct + .reborrow() + .get_named("int8List") + .unwrap() + .downcast(); + assert_eq!(int8_list.len(), 2); + } + + { + let struct_list: capnp::struct_list::Builder<'_, test_all_types::Owned> = root_struct + .reborrow() + .get_named("structList") + .unwrap() + .downcast(); + assert_eq!(struct_list.len(), 3); + } + + { + let enum_list: capnp::enum_list::Builder<'_, crate::test_capnp::TestEnum> = root_struct + .reborrow() + .get_named("enumList") + .unwrap() + .downcast(); + assert_eq!(enum_list.len(), 2); + } + + { + let text_list: capnp::text_list::Builder<'_> = root_struct + .reborrow() + .get_named("textList") + .unwrap() + .downcast(); + assert_eq!(text_list.len(), 3); + assert_eq!(text_list.get(1).unwrap().to_str().unwrap(), "xyzzy"); + } + + { + let data_list: capnp::data_list::Builder<'_> = root_struct + .reborrow() + .get_named("dataList") + .unwrap() + .downcast(); + assert_eq!(data_list.len(), 3); + } + + let root_struct: dynamic_struct::Reader<'_> = root_struct.into_reader(); + { + let int8_list: capnp::primitive_list::Reader<'_, i8> = + root_struct.get_named("int8List").unwrap().downcast(); + assert_eq!(int8_list.len(), 2); + } + + { + let struct_list: capnp::struct_list::Reader<'_, test_all_types::Owned> = + root_struct.get_named("structList").unwrap().downcast(); + assert_eq!(struct_list.len(), 3); + } + + { + let enum_list: capnp::enum_list::Reader<'_, crate::test_capnp::TestEnum> = + root_struct.get_named("enumList").unwrap().downcast(); + assert_eq!(enum_list.len(), 2); + } + + { + let text_list: capnp::text_list::Reader<'_> = + root_struct.get_named("textList").unwrap().downcast(); + assert_eq!(text_list.len(), 3); + assert_eq!(text_list.get(1).unwrap().to_str().unwrap(), "xyzzy"); + } + + { + let data_list: capnp::data_list::Reader<'_> = + root_struct.get_named("dataList").unwrap().downcast(); + assert_eq!(data_list.len(), 3); + } +} diff --git a/capnpc/test/test_util.rs b/capnpc/test/test_util.rs index a705c8318..62f6017a2 100644 --- a/capnpc/test/test_util.rs +++ b/capnpc/test/test_util.rs @@ -430,6 +430,76 @@ pub fn dynamic_init_test_message(mut builder: ::capnp::dynamic_struct::Builder<' substruct.set_named("boolField", true.into()).unwrap(); substruct.set_named("int8Field", (-12i8).into()).unwrap(); substruct.set_named("int16Field", (3456i16).into()).unwrap(); + substruct + .set_named("int32Field", (-78901234i32).into()) + .unwrap(); + substruct + .set_named("int64Field", (56789012345678i64).into()) + .unwrap(); + substruct.set_named("uInt8Field", (90u8).into()).unwrap(); + substruct + .set_named("uInt16Field", (1234u16).into()) + .unwrap(); + substruct + .set_named("uInt32Field", (56789012u32).into()) + .unwrap(); + substruct + .set_named("uInt64Field", (345678901234567890u64).into()) + .unwrap(); + substruct + .set_named("float32Field", (-1.25e-10f32).into()) + .unwrap(); + substruct + .set_named("float64Field", (345f64).into()) + .unwrap(); + substruct.set_named("textField", "baz".into()).unwrap(); + substruct.set_named("dataField", b"qux"[..].into()).unwrap(); + { + let mut subsubstruct = substruct + .reborrow() + .init_named("structField") + .unwrap() + .downcast::<::capnp::dynamic_struct::Builder<'_>>(); + subsubstruct + .set_named("textField", "nested".into()) + .unwrap(); + subsubstruct + .init_named("structField") + .unwrap() + .downcast::<::capnp::dynamic_struct::Builder<'_>>() + .set_named("textField", "really nested".into()) + .unwrap(); + } + substruct + .set_named("enumField", TestEnum::Baz.into()) + .unwrap(); + + substruct.reborrow().initn_named("voidList", 3).unwrap(); + + { + let mut bool_list = substruct + .reborrow() + .initn_named("boolList", 5) + .unwrap() + .downcast::<::capnp::dynamic_list::Builder<'_>>(); + bool_list.set(0, false.into()).unwrap(); + bool_list.set(1, true.into()).unwrap(); + bool_list.set(2, false.into()).unwrap(); + bool_list.set(3, true.into()).unwrap(); + bool_list.set(4, true.into()).unwrap(); + } + + { + let mut int8_list = substruct + .reborrow() + .initn_named("int8List", 4) + .unwrap() + .downcast::<::capnp::dynamic_list::Builder<'_>>(); + int8_list.set(0, 12i8.into()).unwrap(); + int8_list.set(1, (-34i8).into()).unwrap(); + int8_list.set(2, (-0x80i8).into()).unwrap(); + int8_list.set(3, (0x7fi8).into()).unwrap(); + } } builder .set_named("enumField", TestEnum::Corge.into())