view rust/hg-cpython/src/dirstate/dirs_multiset.rs @ 42851:64e28b891796

rust-cpython: mark unsafe functions as such It wasn't trivial to fix leak_immutable() to be safe since we have to allow immutable operations (e.g. iter()) on the leaked reference. So let's mark it unsafe for now. Callers must take care of the returned object to guarantee the memory safety. I'll revisit this later. I think $leaked<T: 'static> could have a function that converts itself into $leaked<U: 'static> with a given FnOnce(&T) -> &U, where T is $inner_struct, and U is $iterator_type for example.
author Yuya Nishihara <yuya@tcha.org>
date Sun, 01 Sep 2019 18:06:31 +0900
parents 8f549c46bc64
children 706104dcb2c8
line wrap: on
line source

// dirs_multiset.rs
//
// Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
//
// This software may be used and distributed according to the terms of the
// GNU General Public License version 2 or any later version.

//! Bindings for the `hg::dirstate::dirs_multiset` file provided by the
//! `hg-core` package.

use std::cell::RefCell;
use std::convert::TryInto;

use cpython::{
    exc, ObjectProtocol, PyBytes, PyClone, PyDict, PyErr, PyObject, PyResult,
    Python,
};

use crate::dirstate::extract_dirstate;
use crate::ref_sharing::{PySharedRefCell, PySharedState};
use hg::{DirsMultiset, DirstateMapError, DirstateParseError, EntryState};

py_class!(pub class Dirs |py| {
    data inner: PySharedRefCell<DirsMultiset>;
    data py_shared_state: PySharedState;

    // `map` is either a `dict` or a flat iterator (usually a `set`, sometimes
    // a `list`)
    def __new__(
        _cls,
        map: PyObject,
        skip: Option<PyObject> = None
    ) -> PyResult<Self> {
        let mut skip_state: Option<EntryState> = None;
        if let Some(skip) = skip {
            skip_state = Some(
                skip.extract::<PyBytes>(py)?.data(py)[0]
                    .try_into()
                    .map_err(|e: DirstateParseError| {
                        PyErr::new::<exc::ValueError, _>(py, e.to_string())
                    })?,
            );
        }
        let inner = if let Ok(map) = map.cast_as::<PyDict>(py) {
            let dirstate = extract_dirstate(py, &map)?;
            DirsMultiset::from_dirstate(&dirstate, skip_state)
        } else {
            let map: Result<Vec<Vec<u8>>, PyErr> = map
                .iter(py)?
                .map(|o| Ok(o?.extract::<PyBytes>(py)?.data(py).to_owned()))
                .collect();
            DirsMultiset::from_manifest(&map?)
        };

        Self::create_instance(
            py,
            PySharedRefCell::new(inner),
            PySharedState::default()
        )
    }

    def addpath(&self, path: PyObject) -> PyResult<PyObject> {
        self.borrow_mut(py)?.add_path(
            path.extract::<PyBytes>(py)?.data(py),
        );
        Ok(py.None())
    }

    def delpath(&self, path: PyObject) -> PyResult<PyObject> {
        self.borrow_mut(py)?.delete_path(
            path.extract::<PyBytes>(py)?.data(py),
        )
            .and(Ok(py.None()))
            .or_else(|e| {
                match e {
                    DirstateMapError::PathNotFound(_p) => {
                        Err(PyErr::new::<exc::ValueError, _>(
                            py,
                            "expected a value, found none".to_string(),
                        ))
                    }
                    DirstateMapError::EmptyPath => {
                        Ok(py.None())
                    }
                }
            })
    }
    def __iter__(&self) -> PyResult<DirsMultisetKeysIterator> {
        let (leak_handle, leaked_ref) = unsafe { self.leak_immutable(py)? };
        DirsMultisetKeysIterator::create_instance(
            py,
            RefCell::new(Some(leak_handle)),
            RefCell::new(Box::new(leaked_ref.iter())),
        )
    }

    def __contains__(&self, item: PyObject) -> PyResult<bool> {
        Ok(self
            .inner(py)
            .borrow()
            .contains(item.extract::<PyBytes>(py)?.data(py).as_ref()))
    }
});

py_shared_ref!(Dirs, DirsMultiset, inner, DirsMultisetLeakedRef,);

impl Dirs {
    pub fn from_inner(py: Python, d: DirsMultiset) -> PyResult<Self> {
        Self::create_instance(
            py,
            PySharedRefCell::new(d),
            PySharedState::default(),
        )
    }

    fn translate_key(py: Python, res: &Vec<u8>) -> PyResult<Option<PyBytes>> {
        Ok(Some(PyBytes::new(py, res)))
    }
}

py_shared_sequence_iterator!(
    DirsMultisetKeysIterator,
    DirsMultisetLeakedRef,
    Vec<u8>,
    Dirs::translate_key,
    Option<PyBytes>
);