zngur_def/
merge.rs

1use crate::{
2    AdditionalIncludes, ConvertPanicToException, CppRef, CppValue, LayoutPolicy, ZngurConstructor,
3    ZngurExternCppFn, ZngurExternCppImpl, ZngurField, ZngurFn, ZngurMethodDetails, ZngurSpec,
4    ZngurTrait, ZngurType,
5};
6
7/// Trait for types with a partial union operation.
8///
9/// If a type T is Merge, it provides a partial union operation `merge`: T x T -> T.
10///
11/// Partial unions do not need to be homogenous. If a type U is Merge<T>,
12/// it provides a partial union operation `merge`: T X U -> U.
13/// For example, T: usize, U: Set<usize>; the partial union is the result of
14/// adding the lhs usize to the rhs Set.
15///
16/// "Partial" means the result is not necessarily defined for all inputs; the union may fail.
17/// This is often because the instances are contradictory (as defined by the type).
18///
19/// There are no guarantees about the state of the mutable argument, `into`, in the case
20/// of a failed merge. `merge` is not required to leave `into` in a valid state, or restore
21/// it to its original state.
22pub trait Merge<T = Self> {
23    /// Writes the partial union of `self` and `into` to the latter.
24    ///
25    /// # Errors
26    ///
27    /// If the instances are contradictory, a `MergeFailure` is returned.
28    fn merge(self, into: &mut T) -> MergeResult;
29}
30
31/// The result of a merge operation.
32pub type MergeResult = Result<(), MergeFailure>;
33
34/// An unsuccessful merge operation.
35pub enum MergeFailure {
36    /// The merge was not successful because of a conflict.
37    Conflict(String),
38}
39
40/// Push an item onto a vector if it is not already present, in linear time.
41fn push_unique<T: Eq>(item: T, smallvec: &mut std::vec::Vec<T>) {
42    if !smallvec.contains(&item) {
43        smallvec.push(item);
44    }
45}
46
47/// Writes the union of `other` and `smallvec` to the latter in O(N * M) time.
48fn inplace_union<T: Eq>(other: Vec<T>, smallvec: &mut std::vec::Vec<T>) {
49    for item in other {
50        push_unique(item, smallvec);
51    }
52}
53
54/// Writes the union of `other` and `smallvec` to the latter in O(N * M) time.
55/// If an element in `other` has the same identity as an element in `smallvec`,
56/// the elements are merged. Otherwise, the element is added to `smallvec`.
57fn merge_by_identity<T: Merge>(
58    other: Vec<T>,
59    smallvec: &mut std::vec::Vec<T>,
60    identity: impl Fn(&T, &T) -> bool,
61) -> MergeResult {
62    for item in other {
63        if let Some(existing) = smallvec.iter_mut().find(|e| identity(e, &item)) {
64            item.merge(existing)?;
65        } else {
66            smallvec.push(item);
67        }
68    }
69    Ok(())
70}
71
72impl<T: Merge> Merge for Option<T> {
73    /// Writes the partial union of `self` and `into` to the latter.
74    ///
75    /// If both `self` and `into` are Some, the underlying values are merged.
76    /// Otherwise, the result is whichever value is Some, or None if neither is.
77    fn merge(self, into: &mut Self) -> MergeResult {
78        match self {
79            Some(src) => match into.as_mut() {
80                Some(dst) => src.merge(dst),
81                None => {
82                    *into = Some(src);
83                    Ok(())
84                }
85            },
86            None => Ok(()),
87        }
88    }
89}
90
91impl<K, V, I: IntoIterator<Item = (K, V)>> Merge<std::collections::HashMap<K, V>> for I
92where
93    K: Eq + std::hash::Hash,
94    V: Merge,
95{
96    /// Merges a sequence of key-value pairs into a hash map.
97    ///
98    /// If a key is present in both `self` and `into`, the corresponding values are merged.
99    /// Otherwise, the entry from `self` is inserted into `into`.
100    ///
101    /// This implementation implies `std::collections::HashMap<K,V>` is `Merge` for all `V: Merge`,
102    /// because HashMap is `IntoIterator`. We use `IntoIterator` to allow literal sequences of
103    /// key-value pairs to be merged into a map.
104    fn merge(self, into: &mut std::collections::HashMap<K, V>) -> MergeResult {
105        for (key, value) in self {
106            match into.entry(key) {
107                std::collections::hash_map::Entry::Vacant(e) => {
108                    e.insert(value);
109                }
110                std::collections::hash_map::Entry::Occupied(mut e) => {
111                    match value.merge(e.get_mut()) {
112                        Ok(()) => {}
113                        Err(message) => {
114                            return Err(message);
115                        }
116                    }
117                }
118            }
119        }
120        Ok(())
121    }
122}
123
124impl Merge for ZngurType {
125    /// Writes the partial union of `self` and `into` to the latter.
126    ///
127    /// PRECONDITION: `self.ty == into.ty`.
128    fn merge(self, into: &mut Self) -> MergeResult {
129        if self.ty != into.ty {
130            panic!(
131                "Attempt to merge different types: {} and {}",
132                self.ty, into.ty
133            );
134        }
135
136        if self.layout != into.layout {
137            return Err(MergeFailure::Conflict(
138                "Duplicate layout policy found".to_string(),
139            ));
140        }
141
142        // TODO: We need to improve the architecture around checking parsing, semantic, and other types of errors.
143        if self.cpp_ref.is_some() && into.layout != LayoutPolicy::ZERO_SIZED_TYPE {
144            return Err(MergeFailure::Conflict(
145                "cpp_ref implies a zero sized stack allocated type".to_string(),
146            ));
147        }
148
149        self.cpp_value.merge(&mut into.cpp_value)?;
150        self.cpp_ref.merge(&mut into.cpp_ref)?;
151
152        inplace_union(self.wellknown_traits, &mut into.wellknown_traits);
153        merge_by_identity(self.methods, &mut into.methods, |a, b| {
154            a.data.name == b.data.name
155        })?;
156        merge_by_identity(self.constructors, &mut into.constructors, |a, b| {
157            a.name == b.name
158        })?;
159        merge_by_identity(self.fields, &mut into.fields, |a, b| a.name == b.name)?;
160
161        Ok(())
162    }
163}
164
165impl Merge for ZngurTrait {
166    /// Writes the partial union of `self` and `into` to the latter.
167    ///
168    /// PRECONDITION: `self.tr == into.tr`.
169    fn merge(self, into: &mut Self) -> MergeResult {
170        if self.tr != into.tr {
171            panic!(
172                "Attempt to merge different traits: {} and {}",
173                self.tr, into.tr
174            );
175        }
176
177        inplace_union(self.methods, &mut into.methods);
178
179        Ok(())
180    }
181}
182
183impl Merge for CppValue {
184    /// Writes the partial union of `self` and `into` to the latter.
185    ///
186    /// There is no meaningful way to merge different CppValues, but we allow
187    /// merging the same CppValue from different sources.
188    fn merge(self, into: &mut Self) -> MergeResult {
189        if self != *into {
190            return Err(MergeFailure::Conflict("Cpp value mismatch".to_string()));
191        }
192        Ok(())
193    }
194}
195
196impl Merge for CppRef {
197    /// Writes the partial union of `self` and `into` to the latter.
198    ///
199    /// There is no meaningful way to merge different CppRefs, but we allow
200    /// merging the same CppRef from different sources.
201    fn merge(self, into: &mut Self) -> MergeResult {
202        if self != *into {
203            return Err(MergeFailure::Conflict("Cpp ref mismatch".to_string()));
204        }
205        Ok(())
206    }
207}
208
209impl Merge<ZngurSpec> for ZngurType {
210    /// Merges a type into a specification's type list.
211    fn merge(self, into: &mut ZngurSpec) -> MergeResult {
212        if let Some(existing) = into.types.iter_mut().find(|t| t.ty == self.ty) {
213            self.merge(existing)?;
214        } else {
215            into.types.push(self);
216        }
217        Ok(())
218    }
219}
220
221impl Merge for ZngurMethodDetails {
222    fn merge(self, into: &mut Self) -> MergeResult {
223        if self != *into {
224            return Err(MergeFailure::Conflict("Method mismatch".to_string()));
225        }
226        Ok(())
227    }
228}
229
230impl Merge for ZngurConstructor {
231    fn merge(self, into: &mut Self) -> MergeResult {
232        if self != *into {
233            return Err(MergeFailure::Conflict("Constructor mismatch".to_string()));
234        }
235        Ok(())
236    }
237}
238
239impl Merge for ZngurField {
240    fn merge(self, into: &mut Self) -> MergeResult {
241        if self != *into {
242            return Err(MergeFailure::Conflict("Field mismatch".to_string()));
243        }
244        Ok(())
245    }
246}
247
248impl Merge<ZngurSpec> for ZngurTrait {
249    /// Merges a trait into a specification's trait list.
250    fn merge(self, into: &mut ZngurSpec) -> MergeResult {
251        [(self.tr.clone(), self)].merge(&mut into.traits)
252    }
253}
254
255impl Merge<ZngurSpec> for ZngurFn {
256    /// Merges a function into a specification's function list.
257    fn merge(self, into: &mut ZngurSpec) -> MergeResult {
258        push_unique(self, &mut into.funcs);
259        Ok(())
260    }
261}
262
263impl Merge<ZngurSpec> for ZngurExternCppFn {
264    /// Merges an extern C++ function into a specification's C++ function list.
265    fn merge(self, into: &mut ZngurSpec) -> MergeResult {
266        push_unique(self, &mut into.extern_cpp_funcs);
267        Ok(())
268    }
269}
270
271impl Merge<ZngurSpec> for ZngurExternCppImpl {
272    /// Merges an extern C++ implementation into a specification's C++ implementation list.
273    fn merge(self, into: &mut ZngurSpec) -> MergeResult {
274        push_unique(self, &mut into.extern_cpp_impls);
275        Ok(())
276    }
277}
278
279impl Merge<ZngurSpec> for AdditionalIncludes {
280    /// Merges #include directives into a specification's additional includes string.
281    fn merge(self, into: &mut ZngurSpec) -> MergeResult {
282        into.additional_includes.0 += self.0.as_str();
283        Ok(())
284    }
285}
286
287impl Merge<ZngurSpec> for ConvertPanicToException {
288    /// Merges a CPtE flag into a specification's CPtE flag.
289    fn merge(self, into: &mut ZngurSpec) -> MergeResult {
290        // TODO: There's an architectural decision here.
291        // I'd like to encode the "unimportability" of CPTE here, rather than in the parser.
292        // But checking this requires knowledge of the parse depth, which seems inappropriate
293        // to pass through here.
294        into.convert_panic_to_exception.0 |= self.0;
295        Ok(())
296    }
297}