diff --git a/src/_macros.rs b/src/_macros.rs index 5f944289f..28727286f 100644 --- a/src/_macros.rs +++ b/src/_macros.rs @@ -29,7 +29,8 @@ macro_rules! panic_on_tskit_error { macro_rules! unsafe_tsk_column_access { ($i: expr, $lo: expr, $hi: expr, $owner: expr, $array: ident) => {{ - if $i < $lo || ($i as $crate::tsk_size_t) >= $hi { + let x = $crate::tsk_id_t::from($i); + if x < $lo || (x as $crate::tsk_size_t) >= $hi { None } else { debug_assert!(!($owner).$array.is_null()); @@ -37,14 +38,15 @@ macro_rules! unsafe_tsk_column_access { // SAFETY: array is not null // and we did our best effort // on bounds checking - Some(unsafe { *$owner.$array.offset($i as isize) }) + Some(unsafe { *$owner.$array.offset(x as isize) }) } else { None } } }}; - ($i: expr, $lo: expr, $hi: expr, $owner: expr, $array: ident, $output_id_type: expr) => {{ - if $i < $lo || ($i as $crate::tsk_size_t) >= $hi { + ($i: expr, $lo: expr, $hi: expr, $owner: expr, $array: ident, $output_id_type: ty) => {{ + let x = $crate::tsk_id_t::from($i); + if x < $lo || (x as $crate::tsk_size_t) >= $hi { None } else { debug_assert!(!($owner).$array.is_null()); @@ -52,7 +54,7 @@ macro_rules! unsafe_tsk_column_access { // SAFETY: array is not null // and we did our best effort // on bounds checking - unsafe { Some($output_id_type(*($owner.$array.offset($i as isize)))) } + unsafe { Some(<$output_id_type>::from(*($owner.$array.offset(x as isize)))) } } else { None } @@ -78,10 +80,11 @@ macro_rules! unsafe_tsk_ragged_column_access { if $owner.$array.is_null() { return None; } + let offset = i.as_usize() as isize; // SAFETY: we have checked bounds and ensured not null - let start = unsafe { *$owner.$offset_array.offset($i as isize) }; + let start = unsafe { *$owner.$offset_array.offset(offset) }; let stop = if i < $hi { - unsafe { *$owner.$offset_array.offset(($i + 1) as isize) } + unsafe { *$owner.$offset_array.offset((offset + 1) as isize) } } else { $owner.$offset_array_len as tsk_size_t }; @@ -145,9 +148,10 @@ macro_rules! unsafe_tsk_ragged_char_column_access_to_slice_u8 { } else { assert!(!$owner.$array.is_null()); assert!(!$owner.$offset_array.is_null()); - let start = unsafe { *$owner.$offset_array.offset($i as isize) }; + let offset = i.as_usize() as isize; + let start = unsafe { *$owner.$offset_array.offset(offset) }; let stop = if i < $hi { - unsafe { *$owner.$offset_array.offset(($i + 1) as isize) } + unsafe { *$owner.$offset_array.offset((offset + 1) as isize) } } else { $owner.$offset_array_len as tsk_size_t }; diff --git a/src/edge_table.rs b/src/edge_table.rs index eb4dec685..adc179cd6 100644 --- a/src/edge_table.rs +++ b/src/edge_table.rs @@ -181,7 +181,7 @@ impl EdgeTable { /// * `None` otherwise. pub fn parent + Copy>(&self, row: E) -> Option { unsafe_tsk_column_access!( - row.into().0, + row.into(), 0, self.num_rows(), self.as_ref(), @@ -197,14 +197,7 @@ impl EdgeTable { /// * `Some(child)` if `u` is valid. /// * `None` otherwise. pub fn child + Copy>(&self, row: E) -> Option { - unsafe_tsk_column_access!( - row.into().0, - 0, - self.num_rows(), - self.as_ref(), - child, - NodeId - ) + unsafe_tsk_column_access!(row.into(), 0, self.num_rows(), self.as_ref(), child, NodeId) } /// Return the ``left`` value from row ``row`` of the table. @@ -215,7 +208,7 @@ impl EdgeTable { /// * `None` otherwise. pub fn left + Copy>(&self, row: E) -> Option { unsafe_tsk_column_access!( - row.into().0, + row.into(), 0, self.num_rows(), self.as_ref(), @@ -231,13 +224,7 @@ impl EdgeTable { /// * `Some(position)` if `u` is valid. /// * `None` otherwise. pub fn right + Copy>(&self, row: E) -> Option { - unsafe_tsk_column_access_and_map_into!( - row.into().0, - 0, - self.num_rows(), - self.as_ref(), - right - ) + unsafe_tsk_column_access_and_map_into!(row.into(), 0, self.num_rows(), self.as_ref(), right) } /// Retrieve decoded metadata for a `row`. @@ -261,7 +248,7 @@ impl EdgeTable { row: EdgeId, ) -> Option> { let table_ref = self.as_ref(); - let buffer = metadata_to_vector!(self, table_ref, row.0)?; + let buffer = metadata_to_vector!(self, table_ref, row.into())?; Some(decode_metadata_row!(T, buffer).map_err(|e| e.into())) } @@ -287,7 +274,7 @@ impl EdgeTable { /// * `Some(row)` if `r` is valid /// * `None` otherwise pub fn row + Copy>(&self, r: E) -> Option { - table_row_access!(r.into().0, self, make_edge_table_row) + table_row_access!(r.into().into(), self, make_edge_table_row) } /// Return a view of row `r` of the table. diff --git a/src/individual_table.rs b/src/individual_table.rs index a995cff84..b7fca8fa1 100644 --- a/src/individual_table.rs +++ b/src/individual_table.rs @@ -171,13 +171,7 @@ impl IndividualTable { /// * `Some(flags)` if `row` is valid. /// * `None` otherwise. pub fn flags + Copy>(&self, row: I) -> Option { - unsafe_tsk_column_access_and_map_into!( - row.into().0, - 0, - self.num_rows(), - self.as_ref(), - flags - ) + unsafe_tsk_column_access_and_map_into!(row.into(), 0, self.num_rows(), self.as_ref(), flags) } /// Return the locations for a given row. @@ -188,7 +182,7 @@ impl IndividualTable { /// * `None` otherwise. pub fn location + Copy>(&self, row: I) -> Option<&[Location]> { unsafe_tsk_ragged_column_access!( - row.into().0, + row.into(), 0, self.num_rows(), self.as_ref(), @@ -207,7 +201,7 @@ impl IndividualTable { /// * `None` otherwise. pub fn parents + Copy>(&self, row: I) -> Option<&[IndividualId]> { unsafe_tsk_ragged_column_access!( - row.into().0, + row.into(), 0, self.num_rows(), self.as_ref(), @@ -393,7 +387,7 @@ match tables.individuals().metadata::(0.into()) row: IndividualId, ) -> Option> { let table_ref = self.as_ref(); - let buffer = metadata_to_vector!(self, table_ref, row.0)?; + let buffer = metadata_to_vector!(self, table_ref, row.into())?; Some(decode_metadata_row!(T, buffer).map_err(|e| e.into())) } @@ -419,7 +413,7 @@ match tables.individuals().metadata::(0.into()) /// * `Some(row)` if `r` is valid /// * `None` otherwise pub fn row + Copy>(&self, r: I) -> Option { - let ri = r.into().0; + let ri = r.into().into(); table_row_access!(ri, self, make_individual_table_row) } diff --git a/src/lib.rs b/src/lib.rs index 9bcf13f2d..27c9b826c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -87,6 +87,7 @@ mod individual_table; pub mod metadata; mod migration_table; mod mutation_table; +mod newtypes; mod node_table; mod population_table; pub mod prelude; @@ -110,347 +111,6 @@ pub use bindings::tsk_flags_t; use bindings::tsk_id_t; use bindings::tsk_size_t; -/// A node ID -/// -/// This is an integer referring to a row of a [``NodeTable``]. -/// The underlying type is [``tsk_id_t``]. -/// -/// # Examples -/// -/// These examples illustrate using this type as something "integer-like". -/// -/// ``` -/// use tskit::NodeId; -/// use tskit::bindings::tsk_id_t; -/// -/// let x: tsk_id_t = 1; -/// let y: NodeId = NodeId::from(x); -/// assert_eq!(x, y); -/// assert_eq!(y, x); -/// -/// assert!(y < x + 1); -/// assert!(y <= x); -/// assert!(x + 1 > y); -/// assert!(x + 1 >= y); -/// -/// let z: NodeId = NodeId::from(x); -/// assert_eq!(y, z); -/// ``` -/// -/// It is also possible to write functions accepting both the `NodeId` -/// and `tsk_id_t`: -/// -/// ``` -/// use tskit::NodeId; -/// use tskit::bindings::tsk_id_t; -/// -/// fn interesting>(x: N) -> NodeId { -/// x.into() -/// } -/// -/// let x: tsk_id_t = 0; -/// assert_eq!(interesting(x), x); -/// let x: NodeId = NodeId::from(0); -/// assert_eq!(interesting(x), x); -/// ``` -/// -/// The types also implement `Display`: -/// -/// ``` -/// use tskit::NodeId; -/// -/// let n = NodeId::from(11); -/// assert_eq!(format!("{}", n), "11".to_string()); -/// // Debug output contains type info -/// assert_eq!(format!("{:?}", n), "NodeId(11)".to_string()); -/// let n = NodeId::from(NodeId::NULL); -/// assert_eq!(format!("{}", n), "NULL"); -/// assert_eq!(format!("{:?}", n), "NodeId(-1)"); -/// ``` -/// -#[repr(transparent)] -#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, std::hash::Hash)] -pub struct NodeId(tsk_id_t); - -/// An individual ID -/// -/// This is an integer referring to a row of an [``IndividualTable``]. -/// -/// The features for this type follow the same pattern as for [``NodeId``] -#[repr(transparent)] -#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, std::hash::Hash)] -pub struct IndividualId(tsk_id_t); - -/// A population ID -/// -/// This is an integer referring to a row of an [``PopulationTable``]. -/// -/// The features for this type follow the same pattern as for [``NodeId``] -#[repr(transparent)] -#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, std::hash::Hash)] -pub struct PopulationId(tsk_id_t); - -/// A site ID -/// -/// This is an integer referring to a row of an [``SiteTable``]. -/// -/// The features for this type follow the same pattern as for [``NodeId``] -#[repr(transparent)] -#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, std::hash::Hash)] -pub struct SiteId(tsk_id_t); - -/// A mutation ID -/// -/// This is an integer referring to a row of an [``MutationTable``]. -/// -/// The features for this type follow the same pattern as for [``NodeId``] -#[repr(transparent)] -#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, std::hash::Hash)] -pub struct MutationId(tsk_id_t); - -/// A migration ID -/// -/// This is an integer referring to a row of an [``MigrationTable``]. -/// -/// The features for this type follow the same pattern as for [``NodeId``] -#[repr(transparent)] -#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, std::hash::Hash)] -pub struct MigrationId(tsk_id_t); - -/// An edge ID -/// -/// This is an integer referring to a row of an [``EdgeTable``]. -/// -/// The features for this type follow the same pattern as for [``NodeId``] -#[repr(transparent)] -#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, std::hash::Hash)] -pub struct EdgeId(tsk_id_t); - -impl_id_traits!(NodeId); -impl_id_traits!(IndividualId); -impl_id_traits!(PopulationId); -impl_id_traits!(SiteId); -impl_id_traits!(MutationId); -impl_id_traits!(MigrationId); -impl_id_traits!(EdgeId); - -impl_size_type_comparisons_for_row_ids!(NodeId); -impl_size_type_comparisons_for_row_ids!(EdgeId); -impl_size_type_comparisons_for_row_ids!(SiteId); -impl_size_type_comparisons_for_row_ids!(MutationId); -impl_size_type_comparisons_for_row_ids!(PopulationId); -impl_size_type_comparisons_for_row_ids!(MigrationId); - -/// Wraps `tsk_size_t` -/// -/// This type plays the role of C's `size_t` in the `tskit` C library. -/// -/// # Examples -/// -/// ``` -/// let s = tskit::SizeType::from(1 as tskit::bindings::tsk_size_t); -/// let mut t: tskit::bindings::tsk_size_t = s.into(); -/// assert!(t == s); -/// assert!(t == 1); -/// let u = tskit::SizeType::from(s); -/// assert!(u == s); -/// t += 1; -/// assert!(t > s); -/// assert!(s < t); -/// ``` -/// -/// #[repr(transparent)] -#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, std::hash::Hash)] -pub struct SizeType(tsk_size_t); - -impl SizeType { - /// Convenience function to convert to usize. - /// - /// Works via [`TryFrom`]. - /// - /// # Returns - /// - /// * `None` if the underlying value is negative. - /// * `Some(usize)` otherwise. - pub fn to_usize(&self) -> Option { - (*self).try_into().ok() - } - - /// Convenience function to convert to usize. - /// Implemented via `as`. - /// Negative values with therefore wrap. - pub fn as_usize(&self) -> usize { - self.0 as usize - } -} - -impl std::fmt::Display for SizeType { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.0) - } -} - -impl From for SizeType { - fn from(value: tsk_size_t) -> Self { - Self(value) - } -} - -impl From for tsk_size_t { - fn from(value: SizeType) -> Self { - value.0 - } -} - -// SizeType is u64, so converstion -// can fail on systems with smaller pointer widths. -impl TryFrom for usize { - type Error = TskitError; - - fn try_from(value: SizeType) -> Result { - match usize::try_from(value.0) { - Ok(x) => Ok(x), - Err(_) => Err(TskitError::RangeError(format!( - "could not convert {} to usize", - value - ))), - } - } -} - -impl TryFrom for SizeType { - type Error = TskitError; - - fn try_from(value: usize) -> Result { - match tsk_size_t::try_from(value) { - Ok(x) => Ok(Self(x)), - Err(_) => Err(TskitError::RangeError(format!( - "could not convert usize {} to SizeType", - value - ))), - } - } -} - -impl TryFrom for SizeType { - type Error = crate::TskitError; - - fn try_from(value: tsk_id_t) -> Result { - match tsk_size_t::try_from(value) { - Ok(v) => Ok(Self(v)), - Err(_) => Err(crate::TskitError::RangeError( - stringify!(value.0).to_string(), - )), - } - } -} - -impl TryFrom for tsk_id_t { - type Error = crate::TskitError; - - fn try_from(value: SizeType) -> Result { - match tsk_id_t::try_from(value.0) { - Ok(v) => Ok(v), - Err(_) => Err(TskitError::RangeError(stringify!(value.0).to_string())), - } - } -} - -impl PartialEq for tsk_size_t { - fn eq(&self, other: &SizeType) -> bool { - *self == other.0 - } -} - -impl PartialEq for SizeType { - fn eq(&self, other: &tsk_size_t) -> bool { - self.0 == *other - } -} - -impl PartialOrd for SizeType { - fn partial_cmp(&self, other: &tsk_size_t) -> Option { - self.0.partial_cmp(other) - } -} - -impl PartialOrd for tsk_size_t { - fn partial_cmp(&self, other: &SizeType) -> Option { - self.partial_cmp(&other.0) - } -} - -/// A newtype for the concept of time. -/// A `Time` value can represent either a point in time -/// or the output of arithmetic involving time. -/// -/// Wraps [`f64`]. -/// -/// # Examples -/// -/// ``` -/// let t0 = tskit::Time::from(2.0); -/// let t1 = tskit::Time::from(10.0); -/// -/// let mut sum = t0 + t1; -/// -/// match sum.partial_cmp(&12.0) { -/// Some(std::cmp::Ordering::Equal) => (), -/// _ => assert!(false), -/// }; -/// -/// sum /= tskit::Time::from(2.0); -/// -/// match sum.partial_cmp(&6.0) { -/// Some(std::cmp::Ordering::Equal) => (), -/// _ => assert!(false), -/// }; -/// ``` -/// -/// # Notes -/// -/// The current implementation of [`PartialOrd`] is based on -/// the underlying implementation for [`f64`]. -/// -/// A `Time` can be multiplied and divided by a [`Position`] -/// -#[repr(transparent)] -#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)] -pub struct Time(f64); - -/// A newtype for the concept of "genomic position". -/// A `Position` can represent either a locus or a -/// distance between loci. -/// -/// Wraps [`f64`]. -/// -/// For examples, see [`Time`]. -/// -/// This type can be multiplied and divided by [`Time`]. -#[repr(transparent)] -#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)] -pub struct Position(f64); - -/// A newtype for the concept of location. -/// A `Location` may represent a location or the -/// output of arithmetic involving locations. -/// -/// Wraps [`f64`]. -/// -/// For examples, see [`Time`]. -/// -#[repr(transparent)] -#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)] -pub struct Location(f64); - -impl_f64_newtypes!(Time); -impl_f64_newtypes!(Position); -impl_f64_newtypes!(Location); - -// It is natural to be able to * and / times and positions -impl_time_position_arithmetic!(Time, Position); -impl_time_position_arithmetic!(Position, Time); - // tskit defines this via a type cast // in a macro. bindgen thus misses it. // See bindgen issue 316. @@ -464,6 +124,7 @@ pub use flags::*; pub use individual_table::{IndividualTable, IndividualTableRow, OwningIndividualTable}; pub use migration_table::{MigrationTable, MigrationTableRow, OwningMigrationTable}; pub use mutation_table::{MutationTable, MutationTableRow, OwningMutationTable}; +pub use newtypes::*; pub use node_table::{NodeTable, NodeTableRow, OwningNodeTable}; pub use population_table::{OwningPopulationTable, PopulationTable, PopulationTableRow}; pub use site_table::{OwningSiteTable, SiteTable, SiteTableRow}; @@ -478,20 +139,6 @@ pub use trees::{Tree, TreeSequence}; #[cfg_attr(doc_cfg, doc(cfg(feature = "provenance")))] pub mod provenance; -#[cfg(feature = "provenance")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "provenance")))] -/// A provenance ID -/// -/// This is an integer referring to a row of a [``provenance::ProvenanceTable``]. -/// -/// The features for this type follow the same pattern as for [``NodeId``] -#[repr(transparent)] -#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, std::hash::Hash)] -pub struct ProvenanceId(tsk_id_t); - -#[cfg(feature = "provenance")] -impl_id_traits!(ProvenanceId); - /// Handles return codes from low-level tskit functions. /// /// When an error from the tskit C API is detected, @@ -539,49 +186,11 @@ pub fn c_api_version() -> String { #[cfg(test)] mod tests { use super::c_api_version; - use super::Location; - use super::Position; - use super::Time; #[test] fn test_c_api_version() { let _ = c_api_version(); } - - #[test] - fn test_f64_newtype_Display() { - let x = Position::from(1.0); - let mut output = String::new(); - std::fmt::write(&mut output, format_args!("{}", x)) - .expect("Error occurred while trying to write in String"); - assert_eq!(output, "1".to_string()); - let x = Time::from(1.0); - let mut output = String::new(); - std::fmt::write(&mut output, format_args!("{}", x)) - .expect("Error occurred while trying to write in String"); - assert_eq!(output, "1".to_string()); - let x = Location::from(1.0); - let mut output = String::new(); - std::fmt::write(&mut output, format_args!("{}", x)) - .expect("Error occurred while trying to write in String"); - assert_eq!(output, "1".to_string()); - } -} - -#[test] -fn test_usize_to_size_type() { - let x = usize::MAX; - let s = SizeType::try_from(x).ok(); - - #[cfg(target_pointer_width = "64")] - assert_eq!(s, Some(bindings::tsk_size_t::MAX.into())); - - #[cfg(target_pointer_width = "32")] - assert_eq!(s, Some((usize::MAX as bindings::tsk_size_t).into())); - - let x = usize::MIN; - let s = SizeType::try_from(x).ok(); - assert_eq!(s, Some(0.into())); } // Testing modules diff --git a/src/migration_table.rs b/src/migration_table.rs index 24d8f624e..1bd8fe344 100644 --- a/src/migration_table.rs +++ b/src/migration_table.rs @@ -199,13 +199,7 @@ impl MigrationTable { /// * `Some(position)` if `row` is valid. /// * `None` otherwise. pub fn left + Copy>(&self, row: M) -> Option { - unsafe_tsk_column_access_and_map_into!( - row.into().0, - 0, - self.num_rows(), - self.as_ref(), - left - ) + unsafe_tsk_column_access_and_map_into!(row.into(), 0, self.num_rows(), self.as_ref(), left) } /// Return the right coordinate for a given row. @@ -215,13 +209,7 @@ impl MigrationTable { /// * `Some(positions)` if `row` is valid. /// * `None` otherwise. pub fn right + Copy>(&self, row: M) -> Option { - unsafe_tsk_column_access_and_map_into!( - row.into().0, - 0, - self.num_rows(), - self.as_ref(), - right - ) + unsafe_tsk_column_access_and_map_into!(row.into(), 0, self.num_rows(), self.as_ref(), right) } /// Return the node for a given row. @@ -231,14 +219,7 @@ impl MigrationTable { /// * `Some(node)` if `row` is valid. /// * `None` otherwise. pub fn node + Copy>(&self, row: M) -> Option { - unsafe_tsk_column_access!( - row.into().0, - 0, - self.num_rows(), - self.as_ref(), - node, - NodeId - ) + unsafe_tsk_column_access!(row.into(), 0, self.num_rows(), self.as_ref(), node, NodeId) } /// Return the source population for a given row. @@ -249,7 +230,7 @@ impl MigrationTable { /// * `None` otherwise. pub fn source + Copy>(&self, row: M) -> Option { unsafe_tsk_column_access!( - row.into().0, + row.into(), 0, self.num_rows(), self.as_ref(), @@ -266,7 +247,7 @@ impl MigrationTable { /// * `None` otherwise. pub fn dest + Copy>(&self, row: M) -> Option { unsafe_tsk_column_access!( - row.into().0, + row.into(), 0, self.num_rows(), self.as_ref(), @@ -282,13 +263,7 @@ impl MigrationTable { /// * `Some(time)` if `row` is valid. /// * `None` otherwise. pub fn time + Copy>(&self, row: M) -> Option