From b8393d9a33a386ae9c2f7cec0d593cd5ef1a4f34 Mon Sep 17 00:00:00 2001 From: Riccardo Mazzarini Date: Tue, 31 Dec 2024 01:27:01 +0800 Subject: [PATCH] Add more methods on `Object` to access underlying values (#210) --- CHANGELOG.md | 11 + crates/api/src/types/command_modifiers.rs | 2 +- crates/types/src/conversion.rs | 4 +- crates/types/src/object.rs | 287 ++++++++++++++++++++-- crates/types/src/serde/de.rs | 9 +- 5 files changed, 290 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d17f2460..8e9e2a1f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,14 @@ a crate containing integration tests annotated with `#[nvim_oxi::test]` ([#201](https://github.com/noib3/nvim-oxi/pull/201)); +- a series of `Object::as_{kind}_unchecked_mut()` methods to get a mutable + reference to an `Object`'s underlying value without performing any runtime + checks; + +- three `Object::as_{string,array,dictionary}_unchecked()` methods to get a + shared reference to an `Object`'s underlying string/array/dictionary without + performing any runtime checks; + ### Changed - `nvim_oxi::api::echo` is now generic over the highlight group type instead of @@ -33,6 +41,9 @@ at its third parameter, and returns `Result` instead of `Result<()>` ([#208](https://github.com/noib3/nvim-oxi/pull/208)); +- renamed `Object::into_dict_unchecked()` to + `Object::into_dictionary_unchecked()`; + ### Removed - the `SetHighlightOptsBuilder::global_link()` method. Use diff --git a/crates/api/src/types/command_modifiers.rs b/crates/api/src/types/command_modifiers.rs index 2c24094f..3fb66d27 100644 --- a/crates/api/src/types/command_modifiers.rs +++ b/crates/api/src/types/command_modifiers.rs @@ -44,6 +44,6 @@ impl ToObject for CommandModifiers { impl From for Dictionary { fn from(mods: CommandModifiers) -> Self { let obj = mods.to_object().unwrap(); - unsafe { obj.into_dict_unchecked() } + unsafe { obj.into_dictionary_unchecked() } } } diff --git a/crates/types/src/conversion.rs b/crates/types/src/conversion.rs index 6a272ed0..ba656bc7 100644 --- a/crates/types/src/conversion.rs +++ b/crates/types/src/conversion.rs @@ -135,7 +135,9 @@ impl FromObject for Array { impl FromObject for Dictionary { fn from_object(obj: Object) -> Result { match obj.kind() { - ObjectKind::Dictionary => Ok(unsafe { obj.into_dict_unchecked() }), + ObjectKind::Dictionary => { + Ok(unsafe { obj.into_dictionary_unchecked() }) + }, other => Err(Error::FromWrongType { expected: "string", diff --git a/crates/types/src/object.rs b/crates/types/src/object.rs index b77088aa..00b678dd 100644 --- a/crates/types/src/object.rs +++ b/crates/types/src/object.rs @@ -148,71 +148,322 @@ impl Object { unsafe { NonOwning::new(std::ptr::read(self)) } } + /// Returns the boolean stored in this [`Object`]. + /// + /// This is a zero-cost method that directly accesses the underlying value + /// without performing any runtime checks. + /// /// # Safety /// - /// TODO + /// This `Object`'s [`ObjectKind`] must be a + /// [`Boolean`][ObjectKind::Boolean]. Calling this method on an `Object` + /// with any other kind may result in undefined behavior. + #[cfg_attr(debug_assertions, track_caller)] #[inline(always)] pub unsafe fn as_boolean_unchecked(&self) -> bool { + debug_assert!(self.ty == ObjectKind::Boolean, "{:?}", self.ty); self.data.boolean } + /// Returns a mutable reference to the boolean stored in this + /// [`Object`]. + /// + /// This is a zero-cost method that directly accesses the underlying + /// value without performing any runtime checks. + /// /// # Safety /// - /// TODO + /// This `Object`'s [`ObjectKind`] must be a + /// [`Boolean`][ObjectKind::Boolean]. Calling this method on an `Object` + /// with any other kind may result in undefined behavior. + #[cfg_attr(debug_assertions, track_caller)] + #[inline(always)] + pub unsafe fn as_boolean_unchecked_mut(&mut self) -> &mut bool { + debug_assert!(self.ty == ObjectKind::Boolean, "{:?}", self.ty); + &mut self.data.boolean + } + + /// Returns the integer stored in this [`Object`]. + /// + /// This is a zero-cost method that directly accesses the underlying + /// value without performing any runtime checks. + /// + /// # Safety + /// + /// This `Object`'s [`ObjectKind`] must be one of + /// [`Integer`][ObjectKind::Integer], [`Buffer`][ObjectKind::Buffer], + /// [`Window`][ObjectKind::Window], or [`TabPage`][ObjectKind::TabPage]. + /// Calling this method on an `Object` with any other kind may result in + /// undefined + /// behavior. + #[cfg_attr(debug_assertions, track_caller)] #[inline(always)] pub unsafe fn as_integer_unchecked(&self) -> Integer { + debug_assert!( + matches!( + self.ty, + ObjectKind::Integer + | ObjectKind::Buffer + | ObjectKind::Window + | ObjectKind::TabPage + ), + "{:?}", + self.ty + ); self.data.integer } + /// Returns a mutable reference to the integer stored in this + /// [`Object`]. + /// + /// This is a zero-cost method that directly accesses the underlying + /// value without performing any runtime checks. + /// /// # Safety /// - /// TODO + /// This `Object`'s [`ObjectKind`] must be one of + /// [`Integer`][ObjectKind::Integer], [`Buffer`][ObjectKind::Buffer], + /// [`Window`][ObjectKind::Window], or [`TabPage`][ObjectKind::TabPage]. + /// Calling this method on an `Object` with any other kind may result in + /// undefined + /// behavior. + #[cfg_attr(debug_assertions, track_caller)] + #[inline(always)] + pub unsafe fn as_integer_unchecked_mut(&mut self) -> &mut Integer { + debug_assert!( + matches!( + self.ty, + ObjectKind::Integer + | ObjectKind::Buffer + | ObjectKind::Window + | ObjectKind::TabPage + ), + "{:?}", + self.ty + ); + &mut self.data.integer + } + + /// Returns the float stored in this [`Object`]. + /// + /// This is a zero-cost method that directly accesses the underlying + /// value without performing any runtime checks. + /// + /// # Safety + /// + /// This `Object`'s [`ObjectKind`] must be a [`Float`][ObjectKind::Float]. + /// Calling this method on an `Object` with any other kind may result in + /// undefined behavior. + #[cfg_attr(debug_assertions, track_caller)] #[inline(always)] pub unsafe fn as_float_unchecked(&self) -> Float { + debug_assert!(self.ty == ObjectKind::Float, "{:?}", self.ty); self.data.float } + /// Returns a mutable reference to the float stored in this + /// [`Object`]. + /// + /// This is a zero-cost method that directly accesses the underlying + /// value without performing any runtime checks. + /// + /// # Safety + /// + /// This `Object`'s [`ObjectKind`] must be a [`Float`][ObjectKind::Float]. + /// Calling this method on an `Object` with any other kind may result in + /// undefined behavior. + #[cfg_attr(debug_assertions, track_caller)] + #[inline(always)] + pub unsafe fn as_float_unchecked_mut(&mut self) -> &mut Float { + debug_assert!(self.ty == ObjectKind::Float, "{:?}", self.ty); + &mut self.data.float + } + + /// Returns the Lua reference stored in this [`Object`]. + /// + /// This is a zero-cost method that directly accesses the underlying + /// value without performing any runtime checks. + /// /// # Safety /// - /// TODO + /// This `Object`'s [`ObjectKind`] must be a + /// [`LuaRef`][ObjectKind::LuaRef]. Calling this method on an `Object` with + /// any other kind may result in undefined behavior. + #[cfg_attr(debug_assertions, track_caller)] #[inline(always)] pub unsafe fn as_luaref_unchecked(&self) -> LuaRef { + debug_assert!(self.ty == ObjectKind::LuaRef, "{:?}", self.ty); self.data.luaref } + /// Returns a mutable reference to the Lua reference stored in this + /// [`Object`]. + /// + /// This is a zero-cost method that directly accesses the underlying + /// value without performing any runtime checks. + /// /// # Safety /// - /// TODO + /// This `Object`'s [`ObjectKind`] must be a + /// [`LuaRef`][ObjectKind::LuaRef]. Calling this method on an `Object` with + /// any other kind may result in undefined behavior. + #[cfg_attr(debug_assertions, track_caller)] + #[inline(always)] + pub unsafe fn as_luaref_unchecked_mut(&mut self) -> &mut LuaRef { + debug_assert!(self.ty == ObjectKind::LuaRef, "{:?}", self.ty); + &mut self.data.luaref + } + + /// Returns a reference to the string stored in this [`Object`]. + /// + /// This is a zero-cost method that directly accesses the underlying + /// value without performing any runtime checks. + /// + /// # Safety + /// + /// This `Object`'s [`ObjectKind`] must be a + /// [`String`][ObjectKind::String]. Calling this method on an `Object` with + /// any other kind may result in undefined behavior. + #[cfg_attr(debug_assertions, track_caller)] + pub unsafe fn as_string_unchecked(&self) -> &crate::String { + debug_assert!(self.ty == ObjectKind::String, "{:?}", self.ty); + &self.data.string + } + + /// Returns a mutable reference to the string stored in this + /// [`Object`]. /// - /// Extracts the contained [`String`](crate::String) value without checking - /// that the object actually contains a [`String`](crate::String). + /// This is a zero-cost method that directly accesses the underlying + /// value without performing any runtime checks. + /// + /// # Safety + /// + /// This `Object`'s [`ObjectKind`] must be a + /// [`String`][ObjectKind::String]. Calling this method on an `Object` with + /// any other kind may result in undefined behavior. + #[cfg_attr(debug_assertions, track_caller)] + pub unsafe fn as_string_unchecked_mut(&mut self) -> &mut crate::String { + debug_assert!(self.ty == ObjectKind::String, "{:?}", self.ty); + &mut self.data.string + } + + /// Returns the string stored in this [`Object`]. + /// + /// This is a zero-cost method that directly accesses the underlying value + /// without performing any runtime checks. + /// + /// # Safety + /// + /// This `Object`'s [`ObjectKind`] must be a + /// [`String`][ObjectKind::String]. Calling this method on an `Object` with + /// any other kind may result in undefined behavior. + #[cfg_attr(debug_assertions, track_caller)] pub unsafe fn into_string_unchecked(self) -> crate::String { + debug_assert!(self.ty == ObjectKind::String, "{:?}", self.ty); #[allow(clippy::unnecessary_struct_initialization)] - let s = crate::String { ..*self.data.string }; + let string = crate::String { ..*self.data.string }; core::mem::forget(self); - s + string } + /// Returns a reference to the array stored in this [`Object`]. + /// + /// This is a zero-cost method that directly accesses the underlying value + /// without performing any runtime checks. + /// /// # Safety /// - /// TODO + /// This `Object`'s [`ObjectKind`] must be an [`Array`][ObjectKind::Array]. + /// Calling this method on an `Object` with any other kind may result in + /// undefined behavior. + #[cfg_attr(debug_assertions, track_caller)] + pub unsafe fn as_array_unchecked(&self) -> &Array { + debug_assert!(self.ty == ObjectKind::Array, "{:?}", self.ty); + &self.data.array + } + + /// Returns a mutable reference to the array stored in this + /// [`Object`]. + /// + /// This is a zero-cost method that directly accesses the underlying value + /// without performing any runtime checks. + /// + /// # Safety + /// + /// This `Object`'s [`ObjectKind`] must be an [`Array`][ObjectKind::Array]. + /// Calling this method on an `Object` with any other kind may result in + /// undefined behavior. + #[cfg_attr(debug_assertions, track_caller)] + pub unsafe fn as_array_unchecked_mut(&mut self) -> &mut Array { + debug_assert!(self.ty == ObjectKind::Array, "{:?}", self.ty); + &mut self.data.array + } + + /// Returns the array stored in this [`Object`]. /// - /// Extracts the contained [`Array`] value without checking that the object - /// actually contains an [`Array`]. + /// This is a zero-cost method that directly accesses the underlying value + /// without performing any runtime checks. + /// + /// # Safety + /// + /// This `Object`'s [`ObjectKind`] must be an [`Array`][ObjectKind::Array]. + /// Calling this method on an `Object` with any other kind may result in + /// undefined behavior. + #[cfg_attr(debug_assertions, track_caller)] pub unsafe fn into_array_unchecked(self) -> Array { + debug_assert!(self.ty == ObjectKind::Array, "{:?}", self.ty); #[allow(clippy::unnecessary_struct_initialization)] let array = Array(crate::kvec::KVec { ..self.data.array.0 }); core::mem::forget(self); array } + /// Returns a reference to the dictionary stored in this [`Object`]. + /// + /// This is a zero-cost method that directly accesses the underlying value + /// without performing any runtime checks. + /// + /// # Safety + /// + /// This `Object`'s [`ObjectKind`] must be a + /// [`Dictionary`][ObjectKind::Dictionary]. Calling this method on an + /// `Object` with any other kind may result in undefined behavior. + #[cfg_attr(debug_assertions, track_caller)] + pub unsafe fn as_dictionary_unchecked(&self) -> &Dictionary { + debug_assert!(self.ty == ObjectKind::Dictionary, "{:?}", self.ty); + &self.data.dictionary + } + + /// Returns a mutable reference to the dictionary stored in this + /// [`Object`]. + /// + /// This is a zero-cost method that directly accesses the underlying value + /// without performing any runtime checks. + /// /// # Safety /// - /// TODO + /// This `Object`'s [`ObjectKind`] must be a + /// [`Dictionary`][ObjectKind::Dictionary]. Calling this method on an + /// `Object` with any other kind may result in undefined behavior. + #[cfg_attr(debug_assertions, track_caller)] + pub unsafe fn as_dictionary_unchecked_mut(&mut self) -> &mut Dictionary { + debug_assert!(self.ty == ObjectKind::Dictionary, "{:?}", self.ty); + &mut self.data.dictionary + } + + /// Returns the dictionary stored in this [`Object`]. + /// + /// This is a zero-cost method that directly accesses the underlying value + /// without performing any runtime checks. /// - /// Extracts the contained [`Dictionary`] value without checking that the - /// object actually contains a [`Dictionary`]. - pub unsafe fn into_dict_unchecked(self) -> Dictionary { + /// # Safety + /// + /// This `Object`'s [`ObjectKind`] must be a + /// [`Dictionary`][ObjectKind::Dictionary]. Calling this method on an + /// `Object` with any other kind may result in undefined behavior. + #[cfg_attr(debug_assertions, track_caller)] + pub unsafe fn into_dictionary_unchecked(self) -> Dictionary { + debug_assert!(self.ty == ObjectKind::Dictionary, "{:?}", self.ty); #[allow(clippy::unnecessary_struct_initialization)] let dict = Dictionary(crate::kvec::KVec { ..self.data.dictionary.0 }); core::mem::forget(self); @@ -473,7 +724,9 @@ impl Pushable for Object { ObjectKind::Float => self.as_float_unchecked().push(lstate), ObjectKind::String => self.into_string_unchecked().push(lstate), ObjectKind::Array => self.into_array_unchecked().push(lstate), - ObjectKind::Dictionary => self.into_dict_unchecked().push(lstate), + ObjectKind::Dictionary => { + self.into_dictionary_unchecked().push(lstate) + }, ObjectKind::LuaRef => { Function::<(), ()>::from_ref(self.as_luaref_unchecked()) .push(lstate) diff --git a/crates/types/src/serde/de.rs b/crates/types/src/serde/de.rs index 9f8d09c5..84ae4f7f 100644 --- a/crates/types/src/serde/de.rs +++ b/crates/types/src/serde/de.rs @@ -96,8 +96,8 @@ impl<'de> de::Deserializer<'de> for Deserializer { { let (s, obj) = match self.obj.kind() { ObjectKind::Dictionary => { - let mut iter = - unsafe { self.obj.into_dict_unchecked() }.into_iter(); + let mut iter = unsafe { self.obj.into_dictionary_unchecked() } + .into_iter(); let (variant, value) = match iter.len() { 1 => iter.next().expect("checked length"), @@ -135,7 +135,8 @@ impl<'de> de::Deserializer<'de> for Deserializer { // Empty dictionaries are also valid arrays. ObjectKind::Dictionary - if (unsafe { self.obj.into_dict_unchecked() }).is_empty() => + if (unsafe { self.obj.into_dictionary_unchecked() }) + .is_empty() => { Array::new() }, @@ -185,7 +186,7 @@ impl<'de> de::Deserializer<'de> for Deserializer { { let dict = match self.obj.kind() { ObjectKind::Dictionary => unsafe { - self.obj.into_dict_unchecked() + self.obj.into_dictionary_unchecked() }, // Empty arrays are also valid dictionaries.