rust/hg-cpython/src/lib.rs
changeset 50979 4c5f6e95df84
parent 49932 136aa80aa8b2
--- a/rust/hg-cpython/src/lib.rs	Thu Aug 10 11:01:07 2023 +0200
+++ b/rust/hg-cpython/src/lib.rs	Fri Aug 18 14:34:29 2023 +0200
@@ -24,6 +24,9 @@
 #![allow(clippy::manual_strip)] // rust-cpython macros
 #![allow(clippy::type_complexity)] // rust-cpython macros
 
+use cpython::{FromPyObject, PyInt, Python, ToPyObject};
+use hg::{BaseRevision, Revision};
+
 /// This crate uses nested private macros, `extern crate` is still needed in
 /// 2018 edition.
 #[macro_use]
@@ -44,6 +47,40 @@
 pub mod revlog;
 pub mod utils;
 
+/// Revision as exposed to/from the Python layer.
+///
+/// We need this indirection because of the orphan rule, meaning we can't
+/// implement a foreign trait (like [`cpython::ToPyObject`])
+/// for a foreign type (like [`hg::UncheckedRevision`]).
+///
+/// This also acts as a deterrent against blindly trusting Python to send
+/// us valid revision numbers.
+#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct PyRevision(BaseRevision);
+
+impl From<Revision> for PyRevision {
+    fn from(r: Revision) -> Self {
+        PyRevision(r.0)
+    }
+}
+
+impl<'s> FromPyObject<'s> for PyRevision {
+    fn extract(
+        py: Python,
+        obj: &'s cpython::PyObject,
+    ) -> cpython::PyResult<Self> {
+        Ok(Self(obj.extract::<BaseRevision>(py)?))
+    }
+}
+
+impl ToPyObject for PyRevision {
+    type ObjectType = PyInt;
+
+    fn to_py_object(&self, py: Python) -> Self::ObjectType {
+        self.0.to_py_object(py)
+    }
+}
+
 py_module_initializer!(rustext, initrustext, PyInit_rustext, |py, m| {
     m.add(
         py,