Auto merge of #131 - faern:implement-propertylist, r=jdm

Add CFPropertyList and methods for casting to subclasses

https://developer.apple.com/documentation/corefoundation/cfpropertylist-rif specifies the following types as valid subclasess of a property list:
CFData, CFString, CFArray, CFDictionary, CFDate, CFBoolean, and CFNumber.

So I add a trait, `CFPropertyListSubClass`, and implement it for the valid types. Then add the `CFPropertyList` type and make it work as much as possible as the other `TCFType`s. However, it can't implement that trait, since it can't implement the static method `type_id`.

The main point of adding this was to then get to the convenience methods `to_CFPropertyList` and `downcast` that allows safe casting between the subclasses and the superclass. This is handy in calls such as `SCDynamicStoreSetValue` and `SCDynamicStoreCopyValue` where the methods either accept or return a `CFPropertyListRef`. With this addition it becomes easy to write safe wrappers around those functions and make them accept proper, constrained types.

Disclaimer: I'm not yet super familiar with CF. So I can only hope this is safe and sound, but I think it is.

<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/core-foundation-rs/131)
<!-- Reviewable:end -->
This commit is contained in:
bors-servo
2017-11-24 10:22:57 -06:00
committed by GitHub
+170 -2
View File
@@ -10,16 +10,18 @@
//! Core Foundation property lists
use std::ptr;
use std::mem;
use libc::c_void;
use error::CFError;
use data::CFData;
use base::{TCFType};
use base::{CFType, TCFType};
pub use core_foundation_sys::propertylist::*;
use core_foundation_sys::error::CFErrorRef;
use core_foundation_sys::base::{kCFAllocatorDefault};
use core_foundation_sys::base::{CFGetRetainCount, CFGetTypeID, CFIndex, CFRelease, CFRetain,
CFShow, CFTypeID, kCFAllocatorDefault};
pub fn create_with_data(data: CFData,
options: CFPropertyListMutabilityOptions)
@@ -56,8 +58,160 @@ pub fn create_data(property_list: *const c_void, format: CFPropertyListFormat) -
}
}
/// Trait for all subclasses of [`CFPropertyList`].
///
/// [`CFPropertyList`]: struct.CFPropertyList.html
pub trait CFPropertyListSubClass<Raw>: TCFType<*const Raw> {
/// Create an instance of the superclass type [`CFPropertyList`] for this instance.
///
/// [`CFPropertyList`]: struct.CFPropertyList.html
fn to_CFPropertyList(&self) -> CFPropertyList {
unsafe { CFPropertyList::wrap_under_get_rule(self.as_concrete_TypeRef() as *const c_void) }
}
}
impl CFPropertyListSubClass<::data::__CFData> for ::data::CFData {}
impl CFPropertyListSubClass<::string::__CFString> for ::string::CFString {}
impl CFPropertyListSubClass<::array::__CFArray> for ::array::CFArray {}
impl CFPropertyListSubClass<::dictionary::__CFDictionary> for ::dictionary::CFDictionary {}
impl CFPropertyListSubClass<::date::__CFDate> for ::date::CFDate {}
impl CFPropertyListSubClass<::number::__CFBoolean> for ::boolean::CFBoolean {}
impl CFPropertyListSubClass<::number::__CFNumber> for ::number::CFNumber {}
/// A CFPropertyList struct. This is superclass to [`CFData`], [`CFString`], [`CFArray`],
/// [`CFDictionary`], [`CFDate`], [`CFBoolean`], and [`CFNumber`].
///
/// This superclass type does not have its own `CFTypeID`, instead each instance has the `CFTypeID`
/// of the subclass it is an instance of. Thus, this type cannot implement the [`TCFType`] trait,
/// since it cannot implement the static [`TCFType::type_id()`] method.
///
/// [`CFData`]: ../data/struct.CFData.html
/// [`CFString`]: ../string/struct.CFString.html
/// [`CFArray`]: ../array/struct.CFArray.html
/// [`CFDictionary`]: ../dictionary/struct.CFDictionary.html
/// [`CFDate`]: ../date/struct.CFDate.html
/// [`CFBoolean`]: ../boolean/struct.CFBoolean.html
/// [`CFNumber`]: ../number/struct.CFNumber.html
/// [`TCFType`]: ../base/trait.TCFType.html
/// [`TCFType::type_id()`]: ../base/trait.TCFType.html#method.type_of
pub struct CFPropertyList(CFPropertyListRef);
impl Drop for CFPropertyList {
fn drop(&mut self) {
unsafe { CFRelease(self.as_CFTypeRef()) }
}
}
impl CFPropertyList {
#[inline]
pub fn as_concrete_TypeRef(&self) -> CFPropertyListRef {
self.0
}
#[inline]
pub unsafe fn wrap_under_get_rule(reference: CFPropertyListRef) -> CFPropertyList {
let reference = mem::transmute(CFRetain(mem::transmute(reference)));
CFPropertyList(reference)
}
#[inline]
pub fn as_CFType(&self) -> CFType {
unsafe { CFType::wrap_under_get_rule(self.as_CFTypeRef()) }
}
#[inline]
pub fn as_CFTypeRef(&self) -> ::core_foundation_sys::base::CFTypeRef {
unsafe { mem::transmute(self.as_concrete_TypeRef()) }
}
#[inline]
pub unsafe fn wrap_under_create_rule(obj: CFPropertyListRef) -> CFPropertyList {
CFPropertyList(obj)
}
/// Returns the reference count of the object. It is unwise to do anything other than test
/// whether the return value of this method is greater than zero.
#[inline]
pub fn retain_count(&self) -> CFIndex {
unsafe { CFGetRetainCount(self.as_CFTypeRef()) }
}
/// Returns the type ID of this object. Will be one of CFData, CFString, CFArray, CFDictionary,
/// CFDate, CFBoolean, or CFNumber.
#[inline]
pub fn type_of(&self) -> CFTypeID {
unsafe { CFGetTypeID(self.as_CFTypeRef()) }
}
/// Writes a debugging version of this object on standard error.
pub fn show(&self) {
unsafe { CFShow(self.as_CFTypeRef()) }
}
/// Returns true if this value is an instance of another type.
#[inline]
pub fn instance_of<OtherConcreteTypeRef, OtherCFType: TCFType<OtherConcreteTypeRef>>(
&self,
) -> bool {
self.type_of() == <OtherCFType as TCFType<_>>::type_id()
}
}
impl Clone for CFPropertyList {
#[inline]
fn clone(&self) -> CFPropertyList {
unsafe { CFPropertyList::wrap_under_get_rule(self.0) }
}
}
impl PartialEq for CFPropertyList {
#[inline]
fn eq(&self, other: &CFPropertyList) -> bool {
self.as_CFType().eq(&other.as_CFType())
}
}
impl Eq for CFPropertyList {}
impl CFPropertyList {
/// Try to downcast the [`CFPropertyList`] to a subclass. Checking if the instance is the correct
/// subclass happens at runtime and an error is returned if it is not the correct type.
/// Works similar to [`Box::downcast`].
///
/// # Examples
///
/// ```
/// # use core_foundation::string::CFString;
/// # use core_foundation::propertylist::{CFPropertyList, CFPropertyListSubClass};
/// #
/// // Create a string.
/// let string: CFString = CFString::from_static_string("FooBar");
/// // Cast it up to a property list.
/// let propertylist: CFPropertyList = string.to_CFPropertyList();
/// // Cast it down again.
/// assert!(propertylist.downcast::<_, CFString>().unwrap().to_string() == "FooBar");
/// ```
///
/// [`CFPropertyList`]: struct.CFPropertyList.html
/// [`Box::downcast`]: https://doc.rust-lang.org/std/boxed/struct.Box.html#method.downcast
pub fn downcast<Raw, T: CFPropertyListSubClass<Raw>>(&self) -> Option<T> {
if self.instance_of::<_, T>() {
Some(unsafe { T::wrap_under_get_rule(self.0 as *const Raw) })
} else {
None
}
}
}
#[cfg(test)]
pub mod test {
use super::*;
use string::CFString;
use boolean::CFBoolean;
#[test]
fn test_property_list_serialization() {
use base::{TCFType, CFEqual};
@@ -84,4 +238,18 @@ pub mod test {
assert!(CFEqual(dict1.as_CFTypeRef(), dict2) == 1);
}
}
#[test]
fn downcast_string() {
let propertylist = CFString::from_static_string("Bar").to_CFPropertyList();
assert!(propertylist.downcast::<_, CFString>().unwrap().to_string() == "Bar");
assert!(propertylist.downcast::<_, CFBoolean>().is_none());
}
#[test]
fn downcast_boolean() {
let propertylist = CFBoolean::true_value().to_CFPropertyList();
assert!(propertylist.downcast::<_, CFBoolean>().is_some());
assert!(propertylist.downcast::<_, CFString>().is_none());
}
}