Skip to main content

tvm_ffi/extra/
unchanged.rs

1/*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements.  See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership.  The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License.  You may obtain a copy of the License at
9 *
10 *   http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied.  See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19
20//! Typed unchanged-or-replacement results for structural mutation.
21
22use std::marker::PhantomData;
23
24use crate::any::{Any, AnyView, TryFromTemp};
25use crate::error::{Error, Result, TYPE_ERROR};
26use crate::tvm_ffi_sys::{TVMFFIAny, TVMFFITypeIndex};
27use crate::type_traits::{AnyCompatible, ContainerElement};
28
29/// A structural mutation that keeps its input without acquiring another owner.
30///
31/// This marker may be returned directly from a map or mutation callback. A
32/// pre-order map still descends into the original value's children.
33#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
34pub struct Unchanged;
35
36// SAFETY: the unchanged ABI tag has no payload or owned resources.
37unsafe impl AnyCompatible for Unchanged {
38    unsafe fn copy_to_any_view(_src: &Self, data: &mut TVMFFIAny) {
39        *data = TVMFFIAny::new();
40        data.type_index = TVMFFITypeIndex::kTVMFFIUnchanged as i32;
41    }
42
43    unsafe fn move_to_any(src: Self, data: &mut TVMFFIAny) {
44        Self::copy_to_any_view(&src, data);
45    }
46
47    unsafe fn check_any_strict(data: &TVMFFIAny) -> bool {
48        data.type_index == TVMFFITypeIndex::kTVMFFIUnchanged as i32
49    }
50
51    unsafe fn copy_from_any_view_after_check(_data: &TVMFFIAny) -> Self {
52        Self
53    }
54
55    unsafe fn move_from_any_after_check(_data: &mut TVMFFIAny) -> Self {
56        Self
57    }
58
59    unsafe fn try_cast_from_any_view(data: &TVMFFIAny) -> std::result::Result<Self, ()> {
60        Self::check_any_strict(data).then_some(Self).ok_or(())
61    }
62
63    fn type_str() -> std::string::String {
64        "Unchanged".into()
65    }
66}
67
68crate::impl_try_from_any!(Unchanged);
69
70/// A typed replacement or an [`Unchanged`] marker, stored in one FFI Any cell.
71///
72/// `T` may be an FFI value type or [`Any`]. Use `Result<UnchangedOr<T>>` to
73/// propagate errors. Callbacks can return this wrapper, [`Unchanged`], a
74/// replacement value, or a `Result` containing any of these.
75///
76/// Converting this wrapper to `Any` preserves the marker. Use
77/// [`Self::value_or`] or [`Self::value_or_else`] when an actual value is needed.
78#[repr(transparent)]
79pub struct UnchangedOr<T: ContainerElement = Any> {
80    data: Any,
81    _marker: PhantomData<T>,
82}
83
84impl<T: ContainerElement> UnchangedOr<T> {
85    /// Keep the original value without borrowing or cloning it.
86    #[inline]
87    pub fn unchanged() -> Self {
88        Self {
89            data: Unchanged.into(),
90            _marker: PhantomData,
91        }
92    }
93
94    /// Supply an owning replacement value.
95    #[inline]
96    pub fn changed(value: T) -> Self {
97        let mut raw = TVMFFIAny::new();
98        // SAFETY: ContainerElement transfers exactly one owning value.
99        unsafe {
100            T::container_move_to_any(value, &mut raw);
101            Self {
102                data: Any::from_raw_ffi_any(raw),
103                _marker: PhantomData,
104            }
105        }
106    }
107
108    /// Whether this result asks its caller to keep the original value.
109    #[inline]
110    pub fn is_unchanged(&self) -> bool {
111        is_unchanged(&self.data)
112    }
113
114    /// Whether the result is unchanged or has the original shallow identity.
115    #[inline]
116    pub fn unchanged_or_same_as(&self, original: &T) -> bool {
117        if self.is_unchanged() {
118            return true;
119        }
120        let mut raw = TVMFFIAny::new();
121        // SAFETY: the borrowed cell is used only while original is alive.
122        unsafe {
123            T::container_copy_to_any_view(original, &mut raw);
124        }
125        super::structural_common::same_shallow(raw, *self.data.as_raw_ffi_any())
126    }
127
128    /// Move out the replacement, returning None when unchanged.
129    #[inline]
130    pub fn into_option(self) -> Option<T> {
131        if self.is_unchanged() {
132            return None;
133        }
134        // SAFETY: constructors and conversions maintain the declared T.
135        unsafe {
136            let mut raw = Any::into_raw_ffi_any(self.data);
137            Some(T::container_move_from_any_after_check(&mut raw))
138        }
139    }
140
141    /// Move the replacement or the supplied original value out of this result.
142    #[inline]
143    pub fn value_or(self, original: T) -> T {
144        self.value_or_else(|| original)
145    }
146
147    /// Materialize the original only if this result is unchanged.
148    #[inline]
149    pub fn value_or_else(self, original: impl FnOnce() -> T) -> T {
150        self.into_option().unwrap_or_else(original)
151    }
152
153    /// Convert only the replacement, preserving an unchanged marker.
154    #[inline]
155    pub fn map<U: ContainerElement>(self, convert: impl FnOnce(T) -> U) -> UnchangedOr<U> {
156        match self.into_option() {
157            Some(value) => UnchangedOr::changed(convert(value)),
158            None => UnchangedOr::unchanged(),
159        }
160    }
161
162    /// Fallibly convert only the replacement, propagating its error unchanged.
163    #[inline]
164    pub fn try_map<U: ContainerElement, E>(
165        self,
166        convert: impl FnOnce(T) -> std::result::Result<U, E>,
167    ) -> std::result::Result<UnchangedOr<U>, E> {
168        match self.into_option() {
169            Some(value) => convert(value).map(UnchangedOr::changed),
170            None => Ok(UnchangedOr::unchanged()),
171        }
172    }
173
174    /// Check a replacement's FFI type without cloning it.
175    ///
176    /// An unchanged result is compatible with every replacement type. Erased
177    /// and narrowing conversions retain the strict type check used by
178    /// [`Any::try_as`]. For a known typed conversion, use `map(Into::into)`.
179    #[inline]
180    pub fn try_cast<U: ContainerElement>(self) -> Result<UnchangedOr<U>> {
181        if unsafe { UnchangedOr::<U>::check_any_strict(self.data.as_raw_ffi_any()) } {
182            Ok(UnchangedOr {
183                data: self.data,
184                _marker: PhantomData,
185            })
186        } else {
187            Err(Error::new(
188                TYPE_ERROR,
189                &format!(
190                    "structural mutation result does not match {}",
191                    U::container_type_str()
192                ),
193                "",
194            ))
195        }
196    }
197}
198
199impl UnchangedOr<Any> {
200    #[inline]
201    pub(crate) fn from_carrier(data: Any) -> Result<Self> {
202        if data.type_index() == TVMFFITypeIndex::kTVMFFIError as i32 {
203            return match Error::try_from(data) {
204                Ok(error) | Err(error) => Err(error),
205            };
206        }
207        Ok(Self {
208            data,
209            _marker: PhantomData,
210        })
211    }
212}
213
214impl<T: ContainerElement> Clone for UnchangedOr<T> {
215    #[inline]
216    fn clone(&self) -> Self {
217        Self {
218            data: self.data.clone(),
219            _marker: PhantomData,
220        }
221    }
222}
223
224// SAFETY: the carrier contains either the resource-free marker or exactly
225// T's owning representation. Borrowing never acquires ownership; moving
226// transfers the carrier once. Non-strict conversions materialize a valid T.
227unsafe impl<T: ContainerElement> AnyCompatible for UnchangedOr<T> {
228    unsafe fn copy_to_any_view(src: &Self, data: &mut TVMFFIAny) {
229        *data = *src.data.as_raw_ffi_any();
230    }
231
232    unsafe fn move_to_any(src: Self, data: &mut TVMFFIAny) {
233        *data = Any::into_raw_ffi_any(src.data);
234    }
235
236    unsafe fn check_any_strict(data: &TVMFFIAny) -> bool {
237        if T::CONTAINER_IS_ANY {
238            // An erased successful result excludes the ABI's error channel.
239            data.type_index != TVMFFITypeIndex::kTVMFFIError as i32
240        } else {
241            Unchanged::check_any_strict(data) || T::container_check_any_strict(data)
242        }
243    }
244
245    unsafe fn copy_from_any_view_after_check(data: &TVMFFIAny) -> Self {
246        if Unchanged::check_any_strict(data) {
247            return Self::unchanged();
248        }
249        // Materialize T, including numeric narrowing, before storing its value.
250        Self::changed(T::container_copy_from_any_view_after_check(data))
251    }
252
253    unsafe fn move_from_any_after_check(data: &mut TVMFFIAny) -> Self {
254        Self {
255            data: Any::from_raw_ffi_any(std::mem::replace(data, TVMFFIAny::new())),
256            _marker: PhantomData,
257        }
258    }
259
260    unsafe fn try_cast_from_any_view(data: &TVMFFIAny) -> std::result::Result<Self, ()> {
261        if T::CONTAINER_IS_ANY && data.type_index == TVMFFITypeIndex::kTVMFFIError as i32 {
262            return Err(());
263        }
264        if Unchanged::check_any_strict(data) {
265            return Ok(Self::unchanged());
266        }
267        T::container_try_cast_from_any_view(data).map(Self::changed)
268    }
269
270    fn type_str() -> std::string::String {
271        format!("UnchangedOr<{}>", T::container_type_str())
272    }
273}
274
275impl<T: ContainerElement> TryFrom<Any> for UnchangedOr<T> {
276    type Error = Error;
277    #[inline]
278    fn try_from(value: Any) -> Result<Self> {
279        TryFromTemp::<Self>::try_from(value).map(TryFromTemp::into_value)
280    }
281}
282
283impl<'a, T: ContainerElement> TryFrom<AnyView<'a>> for UnchangedOr<T> {
284    type Error = Error;
285    #[inline]
286    fn try_from(value: AnyView<'a>) -> Result<Self> {
287        TryFromTemp::<Self>::try_from(value).map(TryFromTemp::into_value)
288    }
289}
290
291#[inline]
292pub(crate) fn is_unchanged(value: &Any) -> bool {
293    value.type_index() == TVMFFITypeIndex::kTVMFFIUnchanged as i32
294}