diff --git a/crates/luajit-bindings/src/ffi.rs b/crates/luajit-bindings/src/ffi.rs index 1e85a5e3..c24846c6 100644 --- a/crates/luajit-bindings/src/ffi.rs +++ b/crates/luajit-bindings/src/ffi.rs @@ -55,7 +55,17 @@ extern "C" { // https://www.lua.org/manual/5.1/manual.html#lua_call pub fn lua_call(L: *mut lua_State, nargs: c_int, nresults: c_int); - // https://www.lua.org/manual/5.1/manual.html#lua_createtable + /// Binding to [`lua_createtable`] (-0, +1). + /// + /// Creates a new empty table and pushes it onto the stack. The new table + /// has space pre-allocated for `narr` array elements and `nrec` non-array + /// elements. + /// + /// This pre-allocation is useful when you know exactly how many + /// elements the table will have. Otherwise you can use the function + /// [`lua_newtable`]. + /// + /// [`lua_createtable`]: https://www.lua.org/manual/5.1/manual.html#lua_createtable pub fn lua_createtable(L: *mut lua_State, narr: c_int, nrec: c_int); // https://www.lua.org/manual/5.1/manual.html#lua_error diff --git a/crates/nvim-api/src/global.rs b/crates/nvim-api/src/global.rs index 7ce48f7a..830fdf6c 100644 --- a/crates/nvim-api/src/global.rs +++ b/crates/nvim-api/src/global.rs @@ -320,9 +320,6 @@ pub fn get_hl_by_id(hl_id: u32, rgb: bool) -> Result { core::ptr::null_mut(), &mut err, ) - // Neovim uses `xmalloc()` to allocate the dictionary when the arena is - // null. - .drop_with_free() }; choose!(err, Ok(HighlightInfos::from_object(hl.into())?)) @@ -342,9 +339,6 @@ pub fn get_hl_by_name(name: &str, rgb: bool) -> Result { core::ptr::null_mut(), &mut err, ) - // Neovim uses `xmalloc()` to allocate the dictionary when the arena is - // null. - .drop_with_free() }; choose!(err, Ok(HighlightInfos::from_object(hl.into())?)) } diff --git a/crates/nvim-api/src/win_config.rs b/crates/nvim-api/src/win_config.rs index add3e4d8..b8ab15bf 100644 --- a/crates/nvim-api/src/win_config.rs +++ b/crates/nvim-api/src/win_config.rs @@ -21,13 +21,15 @@ pub fn open_win( } impl Window { - /// Binding to [`nvim_win_get_config`](https://neovim.io/doc/user/api.html#nvim_win_get_config()). + /// Binding to [`nvim_win_get_config`][1]. /// /// Gets the window configuration. + /// + /// [1]: https://neovim.io/doc/user/api.html#nvim_win_get_config() pub fn get_config(&self) -> Result { let mut err = nvim::Error::new(); let mut dict = unsafe { nvim_win_get_config(self.0, &mut err) }; - let win = dict.get(&"win").map(|obj| unsafe { + let win = dict.get("win").map(|obj| unsafe { // SAFETY: if the `win` key is present it's set to an integer // representing a window handle. obj.as_integer_unchecked() as i32 diff --git a/crates/nvim-types/Cargo.toml b/crates/nvim-types/Cargo.toml index d283c304..98c50d3a 100644 --- a/crates/nvim-types/Cargo.toml +++ b/crates/nvim-types/Cargo.toml @@ -15,10 +15,10 @@ license = "MIT" neovim-0-7 = [] neovim-0-8 = [] neovim-nightly = [] +serde = ["dep:serde"] [dependencies] libc = "0.2" luajit-bindings = { version = "0.2.0", path = "../luajit-bindings" } - serde = { version = "1.0", optional = true } thiserror = "1.0" diff --git a/crates/nvim-types/src/array.rs b/crates/nvim-types/src/array.rs index d00dd694..f429820d 100644 --- a/crates/nvim-types/src/array.rs +++ b/crates/nvim-types/src/array.rs @@ -1,139 +1,160 @@ -use std::ffi::c_int; -use std::fmt::{self, Debug, Display}; -use std::mem::ManuallyDrop; -use std::ptr; +use luajit_bindings as lua; -use luajit_bindings::{self as lua, ffi::lua_State}; +use crate::kvec::{self, KVec}; +use crate::NonOwning; +use crate::Object; -use super::{KVec, Object}; +/// A vector of Neovim [`Object`]s. +#[derive(Clone, Default, PartialEq)] +#[repr(transparent)] +pub struct Array(pub(super) KVec); -// https://github.com/neovim/neovim/blob/master/src/nvim/api/private/defs.h#L89 -// -/// A vector of Neovim [`Object`](Object)s. -pub type Array = KVec; - -impl Debug for Array { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { +impl core::fmt::Debug for Array { + #[inline] + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { f.debug_list().entries(self.iter()).finish() } } -impl Display for Array { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - Debug::fmt(self, f) +impl Array { + /// Returns the number of elements in the array. + #[inline] + pub fn len(&self) -> usize { + self.0.len() } -} -impl lua::Pushable for Array { - unsafe fn push(self, lstate: *mut lua_State) -> Result { - lua::ffi::lua_createtable(lstate, self.len() as _, 0); + /// Returns `true` if the array contains no elements. + #[inline] + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } - for (idx, obj) in self.into_iter().enumerate() { - obj.push(lstate)?; - lua::ffi::lua_rawseti(lstate, -2, (idx + 1) as _); - } + /// Returns an iterator over the `Object`s of the array. + #[inline] + pub fn iter(&self) -> core::slice::Iter<'_, Object> { + self.0.as_slice().iter() + } - Ok(1) + /// Creates a new, empty `Array`. + #[inline] + pub fn new() -> Self { + Self(KVec::new()) + } + + /// Returns a non-owning version of this `Array`. + #[inline] + pub fn non_owning(&self) -> NonOwning<'_, Self> { + #[allow(clippy::unnecessary_struct_initialization)] + NonOwning::new(Self(KVec { ..self.0 })) } } -impl lua::Poppable for Array { - unsafe fn pop(state: *mut lua_State) -> Result { - as lua::Poppable>::pop(state).map(Into::into) +impl> FromIterator for Array { + #[inline] + fn from_iter>(iter: I) -> Self { + Self( + iter.into_iter() + .map(Into::into) + .filter(|obj| obj.is_some()) + .collect(), + ) } } impl IntoIterator for Array { + type Item = Object; type IntoIter = ArrayIterator; - type Item = ::Item; #[inline] fn into_iter(self) -> Self::IntoIter { - // Wrap `self` in `ManuallyDrop` to avoid running destructor. - let arr = ManuallyDrop::new(self); - let start = arr.items; - let end = unsafe { start.add(arr.len()) }; - ArrayIterator { start, end } + ArrayIterator(self.0.into_iter()) } } -impl FromIterator for Array -where - T: Into, -{ - fn from_iter>(iter: I) -> Self { - iter.into_iter() - .map(Into::into) - .filter(Object::is_some) // TODO: maybe avoid this - .collect::>() - .into() - } -} - -/// An owning iterator over the [`Object`]s of a Neovim [`Array`]. -pub struct ArrayIterator { - start: *const Object, - end: *const Object, -} +/// An owning iterator over the `Object`s of a [`Array`]. +#[derive(Clone)] +pub struct ArrayIterator(kvec::IntoIter); impl Iterator for ArrayIterator { type Item = Object; #[inline] fn next(&mut self) -> Option { - if self.start == self.end { - return None; - } - let current = self.start; - self.start = unsafe { self.start.offset(1) }; - Some(unsafe { ptr::read(current) }) + self.0.next() } #[inline] fn size_hint(&self) -> (usize, Option) { - let exact = self.len(); - (exact, Some(exact)) - } - - #[inline] - fn count(self) -> usize { - self.len() + self.0.size_hint() } } impl ExactSizeIterator for ArrayIterator { + #[inline] fn len(&self) -> usize { - unsafe { self.end.offset_from(self.start) as usize } + self.0.len() } } impl DoubleEndedIterator for ArrayIterator { #[inline] fn next_back(&mut self) -> Option { - if self.start == self.end { - return None; + self.0.next_back() + } +} + +impl core::iter::FusedIterator for ArrayIterator {} + +impl lua::Poppable for Array { + #[inline] + unsafe fn pop( + lstate: *mut lua::ffi::lua_State, + ) -> Result { + use lua::ffi::*; + + if lua_gettop(lstate) == 0 { + return Err(lua::Error::PopEmptyStack); + } else if lua_type(lstate, -1) != LUA_TTABLE { + let ty = lua_type(lstate, -1); + return Err(lua::Error::pop_wrong_type::(LUA_TTABLE, ty)); } - let current = self.end; - self.end = unsafe { self.end.offset(-1) }; - Some(unsafe { ptr::read(current) }) + + let mut kvec = KVec::with_capacity(lua_objlen(lstate, -1)); + + lua_pushnil(lstate); + + while lua_next(lstate, -2) != 0 { + kvec.push(Object::pop(lstate)?); + } + + // Pop the table. + lua_pop(lstate, 1); + + Ok(Self(kvec)) } } -impl std::iter::FusedIterator for ArrayIterator {} +impl lua::Pushable for Array { + #[inline] + unsafe fn push( + self, + lstate: *mut lua::ffi::lua_State, + ) -> Result { + use lua::ffi::*; -impl Drop for ArrayIterator { - fn drop(&mut self) { - while self.start != self.end { - unsafe { - ptr::drop_in_place(self.start as *mut Object); - self.start = self.start.offset(1); - } + lua_createtable(lstate, self.len() as _, 0); + + for (idx, obj) in self.into_iter().enumerate() { + obj.push(lstate)?; + lua_rawseti(lstate, -2, (idx + 1) as _); } + + Ok(1) } } -/// Implements `From<(A, B, C, ..)> for Array` for tuples `(A, B, C, ..)` where -/// all the elements in the tuple implement `Into`. +/// Implements `From<(A, B, C, ..)>` for tuples `(A, B, C, ..)` where all the +/// elements in the tuple are `Into`. macro_rules! from_tuple { ($($ty:ident)*) => { impl <$($ty: Into),*> From<($($ty,)*)> for Array { @@ -166,6 +187,13 @@ from_tuple!(A B C D E F G H I J K L M N O P); mod tests { use super::*; + #[test] + fn array_layout() { + use core::alloc::Layout; + + assert_eq!(Layout::new::(), Layout::new::>()); + } + #[test] fn iter_basic() { let array = Array::from_iter(["Foo", "Bar", "Baz"]); @@ -187,19 +215,7 @@ mod tests { #[test] fn empty_array() { - let empty = Array { size: 0, capacity: 0, items: ptr::null_mut() }; + let empty = Array::default(); assert_eq!(0, empty.into_iter().count()); } - - #[test] - fn debug_array() { - let arr = Array::from((1, 2, 3, "a", true)); - assert_eq!(String::from("[1, 2, 3, \"a\", true]"), format!("{arr}")); - } - - #[test] - fn debug_nested_array() { - let arr = Array::from_iter([Array::from((1, 2, 3))]); - assert_eq!(String::from("[[1, 2, 3]]"), format!("{arr}")); - } } diff --git a/crates/nvim-types/src/dictionary.rs b/crates/nvim-types/src/dictionary.rs index f5322835..a8a2bf40 100644 --- a/crates/nvim-types/src/dictionary.rs +++ b/crates/nvim-types/src/dictionary.rs @@ -1,224 +1,316 @@ -use std::collections::HashMap; -use std::ffi::c_int; -use std::mem::ManuallyDrop; -use std::{fmt, ptr}; +use luajit_bindings as lua; -use luajit_bindings::{self as lua, ffi::*, Poppable, Pushable}; +use crate::kvec::{self, KVec}; +use crate::NonOwning; +use crate::Object; -use super::{KVec, Object, String}; +/// A vector of Neovim +/// `(`[`String`](crate::String)`, `[`Object`](crate::Object)`)` pairs. +#[derive(Clone, Default, PartialEq)] +#[repr(transparent)] +pub struct Dictionary(pub(super) KVec); -// https://github.com/neovim/neovim/blob/master/src/nvim/api/private/defs.h#L95 -// -/// A vector of Neovim [`KeyValuePair`] s. -pub type Dictionary = KVec; - -// https://github.com/neovim/neovim/blob/master/src/nvim/api/private/defs.h#L122 -// /// A key-value pair mapping a Neovim [`String`] to a Neovim [`Object`]. +// +// https://github.com/neovim/neovim/blob/v0.8.3/src/nvim/api/private/defs.h#L122-L125 #[derive(Clone, PartialEq)] #[repr(C)] -pub struct KeyValuePair { - pub(crate) key: String, - pub(crate) value: Object, -} - -impl fmt::Debug for KeyValuePair { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Display::fmt(self, f) - } -} - -impl fmt::Display for KeyValuePair { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}: {}", self.key, self.value) - } +pub(super) struct KeyValuePair { + key: crate::String, + value: Object, } -impl From<(K, V)> for KeyValuePair -where - K: Into, - V: Into, -{ - fn from((k, v): (K, V)) -> Self { - Self { key: k.into(), value: v.into() } +impl core::fmt::Debug for Dictionary { + #[inline] + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + f.debug_map().entries(self.iter()).finish() } } impl Dictionary { + /// Returns a reference to the value corresponding to the key. + #[inline] pub fn get(&self, query: &Q) -> Option<&Object> where - String: PartialEq, + Q: ?Sized + PartialEq, { - self.iter() - .find_map(|pair| (&pair.key == query).then_some(&pair.value)) + self.iter().find_map(|(key, value)| (query == key).then_some(value)) } + /// Returns a mutable reference to the value corresponding to the key. + #[inline] pub fn get_mut(&mut self, query: &Q) -> Option<&mut Object> where - String: PartialEq, + Q: ?Sized + PartialEq, { self.iter_mut() - .find_map(|pair| (&pair.key == query).then_some(&mut pair.value)) + .find_map(|(key, value)| (query == key).then_some(value)) + } + + /// Returns `true` if the dictionary contains no elements. + #[inline] + pub fn is_empty(&self) -> bool { + self.0.is_empty() } -} -impl fmt::Debug for Dictionary { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_map() - .entries(self.iter().map(|pair| (&pair.key, &pair.value))) - .finish() + /// Returns an iterator over the `(String, Object)` pairs of the + /// dictionary. + #[inline] + pub fn iter(&self) -> DictIter<'_> { + DictIter(self.0.iter()) } -} -impl fmt::Display for Dictionary { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Debug::fmt(self, f) + /// Returns a mutable iterator over the `(String, Object)` pairs of the + /// dictionary. + #[inline] + pub fn iter_mut(&mut self) -> DictIterMut<'_> { + DictIterMut(self.0.iter_mut()) } -} -impl> std::ops::Index for Dictionary { - type Output = Object; + /// Returns the number of elements in the dictionary. + #[inline] + pub fn len(&self) -> usize { + self.0.len() + } - fn index(&self, index: S) -> &Self::Output { - self.get(&index.into()).unwrap() + /// Creates a new, empty `Dictionary`. + #[inline] + pub fn new() -> Self { + Self(KVec::new()) } -} -impl> std::ops::IndexMut for Dictionary { - fn index_mut(&mut self, index: S) -> &mut Self::Output { - self.get_mut(&index.into()).unwrap() + /// Returns a non-owning version of this `Array`. + #[inline] + pub fn non_owning(&self) -> NonOwning<'_, Self> { + #[allow(clippy::unnecessary_struct_initialization)] + NonOwning::new(Self(KVec { ..self.0 })) } } -impl Pushable for Dictionary { - unsafe fn push(self, lstate: *mut lua_State) -> Result { - lua::ffi::lua_createtable(lstate, 0, self.len() as _); +impl core::ops::Index for Dictionary +where + S: PartialEq, +{ + type Output = Object; - for (key, obj) in self { - lua::ffi::lua_pushlstring(lstate, key.as_ptr(), key.len()); - obj.push(lstate)?; - lua::ffi::lua_rawset(lstate, -3); - } + #[inline] + fn index(&self, index: S) -> &Self::Output { + self.get(&index).unwrap() + } +} - Ok(1) +impl core::ops::IndexMut for Dictionary +where + S: PartialEq, +{ + #[inline] + fn index_mut(&mut self, index: S) -> &mut Self::Output { + self.get_mut(&index).unwrap() } } -impl Poppable for Dictionary { - unsafe fn pop(lstate: *mut lua_State) -> Result { - >::pop(lstate).map(Into::into) +impl FromIterator<(K, V)> for Dictionary +where + K: Into, + V: Into, +{ + #[inline] + fn from_iter>(iter: I) -> Self { + Self( + iter.into_iter() + .filter_map(|(k, v)| { + let value = v.into(); + value + .is_some() + .then(|| KeyValuePair { key: k.into(), value }) + }) + .collect(), + ) } } impl IntoIterator for Dictionary { + type Item = (crate::String, Object); type IntoIter = DictIterator; - type Item = ::Item; #[inline] fn into_iter(self) -> Self::IntoIter { - // Wrap `self` in `ManuallyDrop` to avoid running destructor. - let arr = ManuallyDrop::new(self); - let start = arr.items; - let end = unsafe { start.add(arr.len()) }; - - DictIterator { start, end } + DictIterator(self.0.into_iter()) } } -/// An owning iterator over the ([`String`], [`Object`]) pairs of a Neovim -/// [`Dictionary`]. -pub struct DictIterator { - start: *const KeyValuePair, - end: *const KeyValuePair, -} +/// An owning iterator over the `(String, Object)` pairs of a [`Dictionary`]. +#[derive(Clone)] +pub struct DictIterator(kvec::IntoIter); impl Iterator for DictIterator { - type Item = (String, Object); + type Item = (crate::String, Object); #[inline] fn next(&mut self) -> Option { - if self.start == self.end { - return None; - } - let current = self.start; - self.start = unsafe { self.start.offset(1) }; - let KeyValuePair { key, value } = unsafe { ptr::read(current) }; - Some((key, value)) + self.0.next().map(|pair| (pair.key, pair.value)) } #[inline] fn size_hint(&self) -> (usize, Option) { - let exact = self.len(); - (exact, Some(exact)) + self.0.size_hint() } +} +impl ExactSizeIterator for DictIterator { #[inline] - fn count(self) -> usize { - self.len() + fn len(&self) -> usize { + self.0.len() } } -impl ExactSizeIterator for DictIterator { +impl DoubleEndedIterator for DictIterator { + #[inline] + fn next_back(&mut self) -> Option { + self.0.next_back().map(|pair| (pair.key, pair.value)) + } +} + +impl core::iter::FusedIterator for DictIterator {} + +/// An iterator over the `(String, Object)` pairs of a [`Dictionary`]. +#[derive(Clone)] +pub struct DictIter<'a>(core::slice::Iter<'a, KeyValuePair>); + +impl<'a> Iterator for DictIter<'a> { + type Item = (&'a crate::String, &'a Object); + + #[inline] + fn next(&mut self) -> Option { + self.0.next().map(|pair| (&pair.key, &pair.value)) + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.0.size_hint() + } +} + +impl ExactSizeIterator for DictIter<'_> { #[inline] fn len(&self) -> usize { - unsafe { self.end.offset_from(self.start) as usize } + self.0.len() } } -impl DoubleEndedIterator for DictIterator { +impl DoubleEndedIterator for DictIter<'_> { #[inline] fn next_back(&mut self) -> Option { - if self.start == self.end { - return None; - } - let current = self.end; - self.end = unsafe { self.end.offset(-1) }; - let KeyValuePair { key, value } = unsafe { ptr::read(current) }; - Some((key, value)) + self.0.next_back().map(|pair| (&pair.key, &pair.value)) } } -impl std::iter::FusedIterator for DictIterator {} +impl core::iter::FusedIterator for DictIter<'_> {} -impl Drop for DictIterator { - fn drop(&mut self) { - while self.start != self.end { - unsafe { - ptr::drop_in_place(self.start as *mut Object); - self.start = self.start.offset(1); - } - } +/// A mutable iterator over the `(String, Object)` pairs of a [`Dictionary`]. +pub struct DictIterMut<'a>(core::slice::IterMut<'a, KeyValuePair>); + +impl<'a> Iterator for DictIterMut<'a> { + type Item = (&'a mut crate::String, &'a mut Object); + + #[inline] + fn next(&mut self) -> Option { + self.0.next().map(|pair| (&mut pair.key, &mut pair.value)) + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.0.size_hint() } } -impl FromIterator<(K, V)> for Dictionary -where - K: Into, - V: Into, -{ - fn from_iter>(iter: I) -> Self { - iter.into_iter() - .map(|(k, v)| (k, v.into())) - .filter(|(_, obj)| obj.is_some()) - .map(KeyValuePair::from) - .collect::>() - .into() +impl ExactSizeIterator for DictIterMut<'_> { + #[inline] + fn len(&self) -> usize { + self.0.len() } } -impl From> for Dictionary -where - String: From, - Object: From, -{ - fn from(hashmap: HashMap) -> Self { - hashmap.into_iter().collect() +impl DoubleEndedIterator for DictIterMut<'_> { + #[inline] + fn next_back(&mut self) -> Option { + self.0.next_back().map(|pair| (&mut pair.key, &mut pair.value)) + } +} + +impl core::iter::FusedIterator for DictIterMut<'_> {} + +impl lua::Poppable for Dictionary { + #[inline] + unsafe fn pop( + lstate: *mut lua::ffi::lua_State, + ) -> Result { + use lua::ffi::*; + + if lua_gettop(lstate) == 0 { + return Err(lua::Error::PopEmptyStack); + } else if lua_type(lstate, -1) != LUA_TTABLE { + let ty = lua_type(lstate, -1); + return Err(lua::Error::pop_wrong_type::(LUA_TTABLE, ty)); + } + + let mut kvec = KVec::with_capacity(lua_objlen(lstate, -1)); + + lua_pushnil(lstate); + + while lua_next(lstate, -2) != 0 { + let value = Object::pop(lstate)?; + + // The following `String::pop()` will pop the key, so we push + // another copy on the stack for the next iteration. + lua_pushvalue(lstate, -1); + + let key = crate::String::pop(lstate)?; + + kvec.push(KeyValuePair { key, value }); + } + + // Pop the table. + lua_pop(lstate, 1); + + Ok(Self(kvec)) + } +} + +impl lua::Pushable for Dictionary { + #[inline] + unsafe fn push( + self, + lstate: *mut lua::ffi::lua_State, + ) -> Result { + use lua::ffi::*; + + lua_createtable(lstate, 0, self.len() as _); + + for (key, obj) in self { + lua_pushlstring(lstate, key.as_ptr(), key.len()); + obj.push(lstate)?; + lua_rawset(lstate, -3); + } + + Ok(1) } } #[cfg(test)] mod tests { - use super::{Dictionary, Object, String as NvimString}; + use super::*; + use crate::{Object, String as NvimString}; + + #[test] + fn dict_layout() { + use core::alloc::Layout; + + assert_eq!( + Layout::new::(), + Layout::new::>() + ); + } #[test] fn iter_basic() { @@ -258,28 +350,4 @@ mod tests { iter.next() ); } - - #[test] - fn debug_dict() { - let dict = Dictionary::from_iter([ - ("a", Object::from(1)), - ("b", Object::from(true)), - ("c", Object::from("foobar")), - ]); - - assert_eq!( - String::from("{a: 1, b: true, c: \"foobar\"}"), - format!("{dict}") - ); - } - - #[test] - fn debug_nested_dict() { - let dict = Dictionary::from_iter([( - "foo", - Object::from(Dictionary::from_iter([("a", 1)])), - )]); - - assert_eq!(String::from("{foo: {a: 1}}"), format!("{dict}")); - } } diff --git a/crates/nvim-types/src/kvec.rs b/crates/nvim-types/src/kvec.rs index d800249c..273f8e3d 100644 --- a/crates/nvim-types/src/kvec.rs +++ b/crates/nvim-types/src/kvec.rs @@ -1,25 +1,25 @@ -//! This module contains functionality common to both `Array`s and -//! `Dictionary`s. +//! This module contains the binding to Klib's [`kvec`] type. -use std::ops::{Deref, DerefMut}; -use std::ptr; -use std::slice; +use core::mem; +use core::ops::{Deref, DerefMut}; +use core::ptr; +use core::slice; -use crate::NonOwning; - -// https://github.com/attractivechaos/klib/blob/master/kvec.h#L55 -// -/// Binding to Klib's [`kvec`][1]. +/// Binding to Klib's [`kvec`]. +/// +/// Neovim uses this for its [`Array`] and [`Dictionary`] types. /// -/// [1]: https://github.com/attractivechaos/klib/blob/master/kvec.h +/// [`kvec`]: https://github.com/attractivechaos/klib/blob/master/kvec.h#L55 +/// [`Array`]: https://github.com/neovim/neovim/blob/v0.8.3/src/nvim/api/private/defs.h#L89 +/// [`Dictionary`]: https://github.com/neovim/neovim/blob/v0.8.3/src/nvim/api/private/defs.h#L92 #[repr(C)] -pub struct KVec { +pub(crate) struct KVec { #[cfg(feature = "neovim-0-7")] - pub(crate) items: *mut T, - pub(crate) size: usize, - pub(crate) capacity: usize, + pub(super) items: *mut T, + pub(super) size: usize, + pub(super) capacity: usize, #[cfg(not(feature = "neovim-0-7"))] - pub(crate) items: *mut T, + pub(super) items: *mut T, } impl Default for KVec { @@ -29,85 +29,120 @@ impl Default for KVec { } } -impl KVec { - /// Creates a new empty `Collection`. - #[inline] - pub const fn new() -> Self { - Self { items: std::ptr::null_mut(), size: 0, capacity: 0 } - } - - /// The number of items in the collection. +impl core::fmt::Debug for KVec { #[inline] - pub const fn len(&self) -> usize { - self.size + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + f.debug_list().entries(self.iter()).finish() } +} +impl KVec { + /// Returns a mutable slice of the vector's contents. #[inline] - pub const fn is_empty(&self) -> bool { - self.len() == 0 + pub(crate) fn as_mut_slice(&mut self) -> &mut [T] { + if self.items.is_null() { + &mut [] + } else { + assert!(self.len() * mem::size_of::() <= isize::MAX as usize); + unsafe { slice::from_raw_parts_mut(self.items, self.size) } + } } + /// Returns a slice of the vector's contents. #[inline] pub(crate) fn as_slice(&self) -> &[T] { if self.items.is_null() { &[] } else { + assert!(self.len() * mem::size_of::() <= isize::MAX as usize); unsafe { slice::from_raw_parts(self.items, self.size) } } } + /// Returns the number of elements the vector can hold without reallocating. + #[allow(dead_code)] #[inline] - pub(crate) fn as_mut_slice(&mut self) -> &mut [T] { - unsafe { slice::from_raw_parts_mut(self.items, self.size) } + pub(crate) fn capacity(&self) -> usize { + self.capacity } - /// Drops the collection using `libc::free()` instead of calling `Drop`, - /// returning a new copy that can be dropped as usual. - /// - /// This must always be called on the output of a Neovim function that - /// returns either an `Array` or a `Dictionary` allocated with - /// `malloc()`. + /// Returns the number of elements in the vector. #[inline] - pub unsafe fn drop_with_free(self) -> Self - where - T: Clone, - { - let new = self.clone(); - libc::free(self.items as *mut libc::c_void); - core::mem::forget(self); - new + pub(crate) fn len(&self) -> usize { + self.size } + /// Returns `true` if the vector contains no elements. #[inline] - pub(crate) unsafe fn from_raw_parts( - ptr: *mut T, - size: usize, - capacity: usize, - ) -> Self { - Self { items: ptr, size, capacity } + pub(crate) fn is_empty(&self) -> bool { + self.len() == 0 } - /// Make a non-owning version of this `Collection`. + /// Creates a new, empty `KVec`. #[inline] - #[doc(hidden)] - pub fn non_owning(&self) -> NonOwning<'_, Self> { - NonOwning::new(Self { ..*self }) + pub(crate) fn new() -> Self { + Self { items: core::ptr::null_mut(), size: 0, capacity: 0 } + } + + /// Appends an element to the back of a collection. + /// + /// # Panics + /// + /// Panics if the new capacity exceeds `isize::MAX`. + #[inline] + pub(crate) fn push(&mut self, item: T) { + if self.items.is_null() { + self.capacity = 4; + self.items = unsafe { + libc::malloc(self.capacity * mem::size_of::()) as *mut T + }; + } else if self.size == self.capacity { + self.capacity *= 2; + + assert!( + self.capacity * mem::size_of::() <= isize::MAX as usize + ); + + self.items = unsafe { + libc::realloc( + self.items as *mut libc::c_void, + self.capacity * mem::size_of::(), + ) as *mut T + }; + } + + unsafe { + ptr::write(self.items.add(self.size), item); + } + + self.size += 1; + } + + /// Creates a new, empty `KVec` with the specified capacity. + #[inline] + pub(crate) fn with_capacity(capacity: usize) -> Self { + let items = + unsafe { libc::malloc(capacity * mem::size_of::()) as *mut T }; + + Self { items, size: 0, capacity } } } impl Clone for KVec { + #[inline] fn clone(&self) -> Self { - self.as_slice().to_owned().into() - } -} + let items = unsafe { + libc::malloc(self.capacity * mem::size_of::()) as *mut T + }; -impl Drop for KVec { - fn drop(&mut self) { - unsafe { - ptr::drop_in_place(ptr::slice_from_raw_parts_mut( - self.items, self.size, - )) + for idx in 0..self.size { + unsafe { + let item = &*self.items.add(idx); + ptr::write(items.add(idx), item.clone()); + } } + + Self { items, size: self.size, capacity: self.capacity } } } @@ -121,37 +156,278 @@ impl PartialEq for KVec { impl Deref for KVec { type Target = [T]; + #[inline] fn deref(&self) -> &[T] { self.as_slice() } } impl DerefMut for KVec { + #[inline] fn deref_mut(&mut self) -> &mut [T] { self.as_mut_slice() } } -impl From> for KVec { +impl FromIterator for KVec { #[inline] - fn from(vec: Vec) -> Self { - let size = vec.len(); - let capacity = vec.capacity(); - let ptr = vec.leak() as *mut [T] as *mut T; + fn from_iter>(iter: I) -> Self { + let iter = iter.into_iter(); + let (lo, hi) = iter.size_hint(); + let mut kvec = Self::with_capacity(hi.unwrap_or(lo)); + for item in iter { + kvec.push(item); + } + kvec + } +} + +impl IntoIterator for KVec { + type Item = T; + type IntoIter = IntoIter; - unsafe { Self::from_raw_parts(ptr, size, capacity) } + #[inline] + fn into_iter(self) -> IntoIter { + let ptr = self.items; + let end = unsafe { ptr.add(self.size) }; + mem::forget(self); + IntoIter { ptr, start: ptr, end } } } -impl From> for Vec { +impl Drop for KVec { #[inline] - fn from(coll: KVec) -> Self { - unsafe { - if coll.items.is_null() { - Vec::new() - } else { - Vec::from_raw_parts(coll.items, coll.size, coll.capacity) + fn drop(&mut self) { + if !self.items.is_null() { + // Drop each element before freeing the vector itself. + for idx in 0..self.size { + unsafe { + ptr::drop_in_place(self.items.add(idx)); + } + } + + unsafe { libc::free(self.items as *mut libc::c_void) }; + } + } +} + +pub(crate) struct IntoIter { + /// Points to the start of the `KVec`, used in the `Drop` impl. + ptr: *mut T, + + /// The current position of the forward iterator. + start: *mut T, + + /// One past the current position of the backward iterator. + end: *mut T, +} + +impl Clone for IntoIter { + #[inline] + fn clone(&self) -> Self { + let len = unsafe { self.end.offset_from(self.start) as usize }; + let ptr = unsafe { libc::malloc(len * mem::size_of::()) as *mut T }; + for idx in 0..len { + unsafe { + let item = &*self.start.add(idx); + ptr::write(ptr.add(idx), item.clone()); } } + IntoIter { ptr, start: ptr, end: unsafe { ptr.add(len) } } + } +} + +impl Iterator for IntoIter { + type Item = T; + + #[inline] + fn next(&mut self) -> Option { + if self.start == self.end { + None + } else { + let current = self.start; + self.start = unsafe { self.start.offset(1) }; + Some(unsafe { ptr::read(current) }) + } + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + let exact = self.len(); + (exact, Some(exact)) + } +} + +impl ExactSizeIterator for IntoIter { + #[inline] + fn len(&self) -> usize { + unsafe { self.end.offset_from(self.start) as usize } + } +} + +impl DoubleEndedIterator for IntoIter { + #[inline] + fn next_back(&mut self) -> Option { + if self.start == self.end { + None + } else { + self.end = unsafe { self.end.offset(-1) }; + Some(unsafe { ptr::read(self.end) }) + } + } +} + +impl core::iter::FusedIterator for IntoIter {} + +impl Drop for IntoIter { + #[inline] + fn drop(&mut self) { + // Drop each element before freeing the original `KVec`. + while self.start != self.end { + let current = self.start; + self.start = unsafe { self.start.offset(1) }; + unsafe { ptr::drop_in_place(current) }; + } + + unsafe { libc::free(self.ptr as *mut libc::c_void) }; + } +} + +impl From> for KVec { + #[inline] + fn from(vec: Vec) -> Self { + vec.into_iter().collect() + } +} + +impl From> for Vec { + #[inline] + fn from(kvec: KVec) -> Self { + kvec.into_iter().collect() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn kvec_as_slice() { + let mut kvec = KVec::new(); + kvec.push("foo"); + kvec.push("bar"); + kvec.push("baz"); + assert_eq!(kvec.as_slice(), &["foo", "bar", "baz"]); + } + + #[test] + fn kvec_is_empty() { + let mut kvec = KVec::<&str>::new(); + assert!(kvec.is_empty()); + + kvec.push("foo"); + assert!(!kvec.is_empty()); + } + + #[test] + fn kvec_with_capacity() { + let kvec = KVec::::new(); + assert_eq!(kvec.capacity(), 0); + + let mut kvec = KVec::::with_capacity(2); + assert_eq!(kvec.capacity(), 2); + + kvec.push(1); + kvec.push(2); + kvec.push(3); + assert_eq!(kvec.capacity(), 4); + } + + #[test] + fn kvec_drop() { + let mut kvec = KVec::new(); + kvec.push(String::from("foo")); + kvec.push(String::from("bar")); + kvec.push(String::from("baz")); + assert_eq!(kvec.len(), 3); + drop(kvec); + } + + #[test] + fn kvec_clone() { + let mut kvec = KVec::new(); + kvec.push(String::from("foo")); + kvec.push(String::from("bar")); + kvec.push(String::from("baz")); + + let kvec2 = kvec.clone(); + + assert_eq!(kvec, kvec2); + + drop(kvec); + drop(kvec2); + } + + #[test] + fn kvec_from_iter() { + let kvec = vec!["foo", "bar", "baz"].into_iter().collect::>(); + assert_eq!(kvec.as_slice(), &["foo", "bar", "baz"]); + } + + #[test] + fn kvec_into_iter() { + let kvec: KVec = + ["foo", "bar", "baz"].into_iter().map(Into::into).collect(); + + let mut iter = kvec.into_iter(); + + assert_eq!(Some("foo"), iter.next().as_deref()); + assert_eq!(Some("bar"), iter.next().as_deref()); + assert_eq!(Some("baz"), iter.next().as_deref()); + assert_eq!(None, iter.next()); + } + + #[test] + fn kvec_iter_backward() { + let kvec: KVec = + ["foo", "bar", "baz"].into_iter().map(Into::into).collect(); + + let mut iter = kvec.into_iter(); + + assert_eq!(Some("baz"), iter.next_back().as_deref()); + assert_eq!(Some("bar"), iter.next_back().as_deref()); + assert_eq!(Some("foo"), iter.next_back().as_deref()); + assert_eq!(None, iter.next_back()); + } + + #[test] + fn kvec_iter_drop_halfway() { + let kvec: KVec = + ["foo", "bar", "baz"].into_iter().map(Into::into).collect(); + + let mut iter = kvec.into_iter(); + assert_eq!(Some("foo"), iter.next().as_deref()); + assert_eq!(Some("bar"), iter.next().as_deref()); + drop(iter); + } + + #[test] + fn kvec_iter_clone() { + let kvec: KVec = + ["foo", "bar", "baz"].into_iter().map(Into::into).collect(); + + let mut iter = kvec.into_iter(); + + assert_eq!(Some("foo"), iter.next().as_deref()); + + let mut iter2 = iter.clone(); + + assert_eq!(Some("bar"), iter.next().as_deref()); + assert_eq!(Some("baz"), iter.next().as_deref()); + assert_eq!(None, iter.next()); + + assert_eq!(Some("bar"), iter2.next().as_deref()); + assert_eq!(Some("baz"), iter2.next().as_deref()); + assert_eq!(None, iter2.next()); } } diff --git a/crates/nvim-types/src/lib.rs b/crates/nvim-types/src/lib.rs index dac129bc..39d97dd5 100644 --- a/crates/nvim-types/src/lib.rs +++ b/crates/nvim-types/src/lib.rs @@ -1,5 +1,4 @@ -#![allow(clippy::missing_safety_doc)] -use std::ffi::{c_double, c_int}; +//! Rust bindings to the C types used by Neovim's API. mod array; pub mod conversion; @@ -13,15 +12,22 @@ mod object; pub mod serde; mod string; -pub use array::{Array, ArrayIterator}; -pub use dictionary::{DictIterator, Dictionary, KeyValuePair}; +pub use array::Array; +pub use dictionary::Dictionary; pub use error::Error; pub use function::Function; -pub use kvec::KVec; pub use non_owning::NonOwning; pub use object::{Object, ObjectKind}; pub use string::String; +pub mod iter { + //! Iterators over [`Array`](crate::Array)s and + //! [`Dictionary`](crate::Dictionary)s. + + pub use super::array::ArrayIterator; + pub use super::dictionary::{DictIter, DictIterMut, DictIterator}; +} + // https://github.com/neovim/neovim/blob/master/src/nvim/api/private/defs.h#L67 #[doc(hidden)] pub type Boolean = bool; @@ -32,15 +38,15 @@ pub type Integer = i64; // https://github.com/neovim/neovim/blob/master/src/nvim/api/private/defs.h#L69 #[doc(hidden)] -pub type Float = c_double; +pub type Float = core::ffi::c_double; // https://github.com/neovim/neovim/blob/master/src/nvim/types.h#L23 #[doc(hidden)] -pub type LuaRef = c_int; +pub type LuaRef = core::ffi::c_int; // https://github.com/neovim/neovim/blob/master/src/nvim/types.h#L18 #[allow(non_camel_case_types)] -type handle_T = c_int; +type handle_T = core::ffi::c_int; // https://github.com/neovim/neovim/blob/master/src/nvim/api/private/defs.h#L82 #[doc(hidden)] diff --git a/crates/nvim-types/src/non_owning.rs b/crates/nvim-types/src/non_owning.rs index 209c1db6..dd7bb80d 100644 --- a/crates/nvim-types/src/non_owning.rs +++ b/crates/nvim-types/src/non_owning.rs @@ -1,4 +1,3 @@ -use std::fmt; use std::marker::PhantomData; use std::mem::ManuallyDrop; @@ -6,7 +5,6 @@ use std::mem::ManuallyDrop; /// /// Used for FFI functions that accept data by value, but don't destroy or move /// out of it. This is guaranteed to have the same layout as `T`. -#[doc(hidden)] #[repr(transparent)] pub struct NonOwning<'a, T> { inner: ManuallyDrop, @@ -19,11 +17,12 @@ impl<'a, T> NonOwning<'a, T> { } } -impl<'a, T> fmt::Debug for NonOwning<'a, T> +impl<'a, T> core::fmt::Debug for NonOwning<'a, T> where - T: fmt::Debug, + T: core::fmt::Debug, { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + #[inline] + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { self.inner.fmt(f) } } @@ -32,6 +31,7 @@ impl<'a, T> Default for NonOwning<'a, T> where T: Default, { + #[inline] fn default() -> Self { Self { inner: ManuallyDrop::new(T::default()), lt: PhantomData } } diff --git a/crates/nvim-types/src/object.rs b/crates/nvim-types/src/object.rs index bd1670ed..25708010 100644 --- a/crates/nvim-types/src/object.rs +++ b/crates/nvim-types/src/object.rs @@ -1,6 +1,5 @@ use std::borrow::Cow; use std::ffi::c_int; -use std::fmt; use std::mem::ManuallyDrop; use lua::{ffi::*, Poppable, Pushable}; @@ -83,27 +82,34 @@ impl Default for Object { } } -impl fmt::Debug for Object { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Display::fmt(self, f) - } -} +impl core::fmt::Debug for Object { + #[inline] + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + let field: &dyn core::fmt::Debug = match self.ty { + ObjectKind::Nil => &"()", -impl fmt::Display for Object { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use ObjectKind::*; - match self.ty { - Nil => f.write_str("()"), - Boolean => write!(f, "{}", unsafe { self.data.boolean }), - Integer | Buffer | Window | TabPage => { - write!(f, "{}", unsafe { self.data.integer }) + ObjectKind::Boolean => unsafe { &self.data.boolean }, + + ObjectKind::Integer + | ObjectKind::Buffer + | ObjectKind::Window + | ObjectKind::TabPage => unsafe { &self.data.integer }, + + ObjectKind::Float => unsafe { &self.data.float }, + + ObjectKind::String => unsafe { &*self.data.string }, + + ObjectKind::Array => unsafe { &*self.data.array }, + + ObjectKind::Dictionary => unsafe { &*self.data.dictionary }, + + ObjectKind::LuaRef => { + let luaref = unsafe { self.data.luaref }; + return write!(f, "Object(LuaRef({}))", luaref); }, - Float => write!(f, "{}", unsafe { self.data.float }), - String => write!(f, "\"{}\"", unsafe { &*self.data.string }), - Array => write!(f, "{}", unsafe { &*self.data.array }), - Dictionary => write!(f, "{}", unsafe { &*self.data.dictionary }), - LuaRef => write!(f, "LuaRef({})", unsafe { self.data.luaref }), - } + }; + + f.debug_tuple("Object").field(field).finish() } } @@ -165,25 +171,28 @@ impl Object { /// Extracts the contained [`String`](crate::String) value without checking /// that the object actually contains a [`String`](crate::String). pub unsafe fn into_string_unchecked(self) -> crate::String { - let str = ManuallyDrop::new(self); #[allow(clippy::unnecessary_struct_initialization)] - crate::String { ..*str.data.string } + let s = crate::String { ..*self.data.string }; + core::mem::forget(self); + s } /// Extracts the contained [`Array`] value without checking that the object /// actually contains an [`Array`]. pub unsafe fn into_array_unchecked(self) -> Array { - let array = ManuallyDrop::new(self); #[allow(clippy::unnecessary_struct_initialization)] - Array { ..*array.data.array } + let array = Array(crate::kvec::KVec { ..self.data.array.0 }); + core::mem::forget(self); + array } /// Extracts the contained [`Dictionary`] value without checking that the /// object actually contains a [`Dictionary`]. pub unsafe fn into_dict_unchecked(self) -> Dictionary { - let dict = ManuallyDrop::new(self); #[allow(clippy::unnecessary_struct_initialization)] - Dictionary { ..*dict.data.dictionary } + let dict = Dictionary(crate::kvec::KVec { ..self.data.dictionary.0 }); + core::mem::forget(self); + dict } } @@ -628,63 +637,4 @@ mod tests { assert!(str_again.is_ok()); assert_eq!(str, str_again.unwrap()); } - - #[test] - fn print_nil() { - let obj = Object::nil(); - assert_eq!("()", &format!("{obj:?}")); - assert_eq!("()", &format!("{obj}")); - } - - #[test] - fn print_boolean() { - let obj = Object::from(true); - assert_eq!("true", &format!("{obj:?}")); - assert_eq!("true", &format!("{obj}")); - } - - #[test] - fn print_integer() { - let obj = Object::from(42); - assert_eq!("42", &format!("{obj:?}")); - assert_eq!("42", &format!("{obj}")); - } - - #[test] - fn print_float() { - let obj = Object::from(42.1); - assert_eq!("42.1", &format!("{obj:?}")); - assert_eq!("42.1", &format!("{obj}")); - } - - #[test] - fn print_string() { - let obj = Object::from("foobar"); - assert_eq!("\"foobar\"", &format!("{obj:?}")); - assert_eq!("\"foobar\"", &format!("{obj}")); - } - - #[test] - fn print_array() { - let obj = Object::from(Array::from((42.1, true, "foo"))); - assert_eq!("[42.1, true, \"foo\"]", &format!("{obj:?}")); - assert_eq!("[42.1, true, \"foo\"]", &format!("{obj}")); - } - - #[test] - fn print_dict() { - let obj = Object::from(Dictionary::from_iter([ - ("foo", Object::from("bar")), - ("baz", Object::from(19)), - ])); - assert_eq!("{foo: \"bar\", baz: 19}", &format!("{obj:?}")); - assert_eq!("{foo: \"bar\", baz: 19}", &format!("{obj}")); - } - - #[test] - fn print_luaref() { - let obj = Object::from_luaref(42); - assert_eq!("LuaRef(42)", &format!("{obj:?}")); - assert_eq!("LuaRef(42)", &format!("{obj}")); - } } diff --git a/crates/nvim-types/src/serde/de.rs b/crates/nvim-types/src/serde/de.rs index 0efb31bb..aec5ae45 100644 --- a/crates/nvim-types/src/serde/de.rs +++ b/crates/nvim-types/src/serde/de.rs @@ -205,7 +205,7 @@ impl<'de> de::Deserializer<'de> for Deserializer { } struct SeqDeserializer { - iter: crate::ArrayIterator, + iter: crate::iter::ArrayIterator, } impl<'de> de::SeqAccess<'de> for SeqDeserializer { @@ -228,7 +228,7 @@ impl<'de> de::SeqAccess<'de> for SeqDeserializer { } struct MapDeserializer { - iter: crate::DictIterator, + iter: crate::iter::DictIterator, obj: Option, } diff --git a/crates/nvim-types/src/string.rs b/crates/nvim-types/src/string.rs index ed6c7fc0..a4d7adad 100644 --- a/crates/nvim-types/src/string.rs +++ b/crates/nvim-types/src/string.rs @@ -153,12 +153,7 @@ impl Clone for String { } impl Drop for String { - fn drop(&mut self) { - // One extra for null terminator. - let _ = unsafe { - Vec::from_raw_parts(self.data, self.size + 1, self.size + 1) - }; - } + fn drop(&mut self) {} } impl From for String { @@ -234,6 +229,13 @@ impl PartialEq for String { } } +impl PartialEq for str { + #[inline] + fn eq(&self, other: &String) -> bool { + other == self + } +} + impl PartialEq<&str> for String { #[inline] fn eq(&self, other: &&str) -> bool { @@ -241,6 +243,13 @@ impl PartialEq<&str> for String { } } +impl PartialEq for &str { + #[inline] + fn eq(&self, other: &String) -> bool { + other == self + } +} + impl PartialEq for String { #[inline] fn eq(&self, other: &StdString) -> bool {