Skip to main content

cairo_lang_semantic/
stable_order.rs

1//! A total order on inference-free semantic objects that is consistent across builds: unlike the
2//! order of their ids, it depends only on the definitions they are built from and the values of
3//! their constants.
4
5use std::cmp::Ordering;
6
7use cairo_lang_defs::ids::{FunctionTitleId, LanguageElementId};
8use cairo_lang_defs::stable_order::{cmp_elements, cmp_nodes};
9use salsa::Database;
10
11use crate::items::constant::{ConstValue, ConstValueId};
12use crate::items::enm::ConcreteVariant;
13use crate::items::functions::{
14    ConcreteFunctionWithBodyId, GenericFunctionId, GenericFunctionWithBodyId,
15};
16use crate::items::imp::{
17    GeneratedImplItems, ImplId, ImplLongId, NegativeImplId, NegativeImplLongId, UninferredImpl,
18};
19use crate::items::trt::ConcreteTraitId;
20use crate::types::{TypeId, TypeLongId};
21use crate::{FunctionId, GenericArgumentId};
22
23/// A semantic object with a place in the build-independent order.
24pub trait StableOrd<'db> {
25    /// Compares two objects of the same type; distinct inference-free objects never compare equal.
26    fn stable_cmp(&self, other: &Self, db: &'db dyn Database) -> Ordering;
27}
28
29/// Compares two sequences lexicographically.
30pub fn cmp_slices<'db, T: StableOrd<'db>>(db: &'db dyn Database, a: &[T], b: &[T]) -> Ordering {
31    a.iter()
32        .zip(b)
33        .map(|(a, b)| a.stable_cmp(b, db))
34        .find(|ordering| ordering.is_ne())
35        .unwrap_or(a.len().cmp(&b.len()))
36}
37
38/// Compares two generic definitions applied to generic arguments.
39fn cmp_concrete<'db>(
40    db: &'db dyn Database,
41    (a, a_args): (&impl LanguageElementId<'db>, &[GenericArgumentId<'db>]),
42    (b, b_args): (&impl LanguageElementId<'db>, &[GenericArgumentId<'db>]),
43) -> Ordering {
44    cmp_elements(db, a, b).then_with(|| cmp_slices(db, a_args, b_args))
45}
46
47/// `Ok` values precede errors, which are all equal.
48impl<'db, T: StableOrd<'db>, E> StableOrd<'db> for Result<T, E> {
49    fn stable_cmp(&self, other: &Self, db: &'db dyn Database) -> Ordering {
50        match (self, other) {
51            (Ok(a), Ok(b)) => a.stable_cmp(b, db),
52            (Ok(_), Err(_)) => Ordering::Less,
53            (Err(_), Ok(_)) => Ordering::Greater,
54            (Err(_), Err(_)) => Ordering::Equal,
55        }
56    }
57}
58
59impl<'db> StableOrd<'db> for GenericArgumentId<'db> {
60    fn stable_cmp(&self, other: &Self, db: &'db dyn Database) -> Ordering {
61        let kind = |arg: &Self| match arg {
62            Self::Type(_) => 0,
63            Self::Constant(_) => 1,
64            Self::Impl(_) => 2,
65            Self::NegImpl(_) => 3,
66        };
67        match (self, other) {
68            (Self::Type(a), Self::Type(b)) => a.stable_cmp(b, db),
69            (Self::Constant(a), Self::Constant(b)) => a.stable_cmp(b, db),
70            (Self::Impl(a), Self::Impl(b)) => a.stable_cmp(b, db),
71            (Self::NegImpl(a), Self::NegImpl(b)) => a.stable_cmp(b, db),
72            (a, b) => kind(a).cmp(&kind(b)),
73        }
74    }
75}
76
77impl<'db> StableOrd<'db> for TypeId<'db> {
78    fn stable_cmp(&self, other: &Self, db: &'db dyn Database) -> Ordering {
79        use TypeLongId::*;
80        let kind = |ty: &TypeLongId<'db>| match ty {
81            Concrete(_) => 0,
82            Tuple(_) => 1,
83            Snapshot(_) => 2,
84            GenericParameter(_) => 3,
85            Var(_) => 4,
86            NumericLiteral(_) => 5,
87            Coupon(_) => 6,
88            FixedSizeArray { .. } => 7,
89            ImplType(_) => 8,
90            Closure(_) => 9,
91            Missing(_) => 10,
92        };
93        match (self.long(db), other.long(db)) {
94            (Concrete(a), Concrete(b)) => {
95                cmp_elements(db, &a.generic_type(db), &b.generic_type(db))
96                    .then_with(|| cmp_slices(db, &a.generic_args(db), &b.generic_args(db)))
97            }
98            (Tuple(a), Tuple(b)) => cmp_slices(db, a, b),
99            (Snapshot(a), Snapshot(b)) => a.stable_cmp(b, db),
100            (GenericParameter(a), GenericParameter(b)) => cmp_elements(db, a, b),
101            (Var(a), Var(b)) | (NumericLiteral(a), NumericLiteral(b)) => a.id.0.cmp(&b.id.0),
102            (Coupon(a), Coupon(b)) => a.stable_cmp(b, db),
103            (
104                FixedSizeArray { type_id: a, size: a_size },
105                FixedSizeArray { type_id: b, size: b_size },
106            ) => a.stable_cmp(b, db).then_with(|| a_size.stable_cmp(b_size, db)),
107            (ImplType(a), ImplType(b)) => a
108                .impl_id()
109                .stable_cmp(&b.impl_id(), db)
110                .then_with(|| cmp_elements(db, &a.ty(), &b.ty())),
111            (Closure(a), Closure(b)) => {
112                cmp_nodes(db, a.params_location.stable_ptr(), b.params_location.stable_ptr())
113                    .then_with(|| a.parent_function.stable_cmp(&b.parent_function, db))
114            }
115            (Missing(_), Missing(_)) => Ordering::Equal,
116            (a, b) => kind(a).cmp(&kind(b)),
117        }
118    }
119}
120
121impl<'db> StableOrd<'db> for ConstValueId<'db> {
122    fn stable_cmp(&self, other: &Self, db: &'db dyn Database) -> Ordering {
123        use ConstValue::*;
124        let kind = |value: &ConstValue<'db>| match value {
125            Int(..) => 0,
126            Struct(..) => 1,
127            Enum(..) => 2,
128            NonZero(_) => 3,
129            Generic(_) => 4,
130            ImplConstant(_) => 5,
131            Var(..) => 6,
132            Missing(_) => 7,
133        };
134        match (self.long(db), other.long(db)) {
135            (Int(a, a_ty), Int(b, b_ty)) => a.cmp(b).then_with(|| a_ty.stable_cmp(b_ty, db)),
136            (Struct(a, a_ty), Struct(b, b_ty)) => {
137                a_ty.stable_cmp(b_ty, db).then_with(|| cmp_slices(db, a, b))
138            }
139            (Enum(a, a_payload), Enum(b, b_payload)) => {
140                a.stable_cmp(b, db).then_with(|| a_payload.stable_cmp(b_payload, db))
141            }
142            (NonZero(a), NonZero(b)) => a.stable_cmp(b, db),
143            (Generic(a), Generic(b)) => cmp_elements(db, a, b),
144            (ImplConstant(a), ImplConstant(b)) => a
145                .impl_id()
146                .stable_cmp(&b.impl_id(), db)
147                .then_with(|| cmp_elements(db, &a.trait_constant_id(), &b.trait_constant_id())),
148            (Var(a, a_ty), Var(b, b_ty)) => {
149                a.id.0.cmp(&b.id.0).then_with(|| a_ty.stable_cmp(b_ty, db))
150            }
151            (Missing(_), Missing(_)) => Ordering::Equal,
152            (a, b) => kind(a).cmp(&kind(b)),
153        }
154    }
155}
156
157impl<'db> StableOrd<'db> for ConcreteVariant<'db> {
158    fn stable_cmp(&self, other: &Self, db: &'db dyn Database) -> Ordering {
159        cmp_concrete(
160            db,
161            (&self.concrete_enum_id.enum_id(db), &self.concrete_enum_id.long(db).generic_args),
162            (&other.concrete_enum_id.enum_id(db), &other.concrete_enum_id.long(db).generic_args),
163        )
164        .then_with(|| cmp_elements(db, &self.id, &other.id))
165    }
166}
167
168impl<'db> StableOrd<'db> for ImplId<'db> {
169    fn stable_cmp(&self, other: &Self, db: &'db dyn Database) -> Ordering {
170        use ImplLongId::*;
171        let kind = |imp: &ImplLongId<'db>| match imp {
172            Concrete(_) => 0,
173            GenericParameter(_) => 1,
174            ImplVar(_) => 2,
175            ImplImpl(_) => 3,
176            SelfImpl(_) => 4,
177            GeneratedImpl(_) => 5,
178        };
179        match (self.long(db), other.long(db)) {
180            (Concrete(a), Concrete(b)) => {
181                let (a, b) = (a.long(db), b.long(db));
182                cmp_concrete(
183                    db,
184                    (&a.impl_def_id, &a.generic_args),
185                    (&b.impl_def_id, &b.generic_args),
186                )
187            }
188            (GenericParameter(a), GenericParameter(b)) => cmp_elements(db, a, b),
189            (ImplVar(a), ImplVar(b)) => a.long(db).id.0.cmp(&b.long(db).id.0),
190            (ImplImpl(a), ImplImpl(b)) => a
191                .impl_id()
192                .stable_cmp(&b.impl_id(), db)
193                .then_with(|| cmp_elements(db, &a.trait_impl_id(), &b.trait_impl_id())),
194            (SelfImpl(a), SelfImpl(b)) => a.stable_cmp(b, db),
195            (GeneratedImpl(a), GeneratedImpl(b)) => {
196                let (a, b) = (a.long(db), b.long(db));
197                a.concrete_trait
198                    .stable_cmp(&b.concrete_trait, db)
199                    .then_with(|| a.impl_items.stable_cmp(&b.impl_items, db))
200            }
201            (a, b) => kind(a).cmp(&kind(b)),
202        }
203    }
204}
205
206impl<'db> StableOrd<'db> for NegativeImplId<'db> {
207    fn stable_cmp(&self, other: &Self, db: &'db dyn Database) -> Ordering {
208        use NegativeImplLongId::*;
209        let kind = |imp: &NegativeImplLongId<'db>| match imp {
210            Solved(_) => 0,
211            GenericParameter(_) => 1,
212            NegativeImplVar(_) => 2,
213        };
214        match (self.long(db), other.long(db)) {
215            (Solved(a), Solved(b)) => a.stable_cmp(b, db),
216            (GenericParameter(a), GenericParameter(b)) => cmp_elements(db, a, b),
217            (NegativeImplVar(a), NegativeImplVar(b)) => a.long(db).id.0.cmp(&b.long(db).id.0),
218            (a, b) => kind(a).cmp(&kind(b)),
219        }
220    }
221}
222
223/// Compared item by item, as a sequence of trait type and value pairs.
224impl<'db> StableOrd<'db> for GeneratedImplItems<'db> {
225    fn stable_cmp(&self, other: &Self, db: &'db dyn Database) -> Ordering {
226        self.0
227            .iter()
228            .zip(other.0.iter())
229            .map(|((a_ty, a), (b_ty, b))| {
230                cmp_elements(db, a_ty, b_ty).then_with(|| a.stable_cmp(b, db))
231            })
232            .find(|ordering| ordering.is_ne())
233            .unwrap_or(self.0.len().cmp(&other.0.len()))
234    }
235}
236
237impl<'db> StableOrd<'db> for ConcreteTraitId<'db> {
238    fn stable_cmp(&self, other: &Self, db: &'db dyn Database) -> Ordering {
239        let (a, b) = (self.long(db), other.long(db));
240        cmp_concrete(db, (&a.trait_id, &a.generic_args), (&b.trait_id, &b.generic_args))
241    }
242}
243
244/// Functions are ordered by the position of their definition first (the trait function for impl
245/// functions), so that the earliest defined function of a group comes first regardless of kind.
246impl<'db> StableOrd<'db> for FunctionId<'db> {
247    fn stable_cmp(&self, other: &Self, db: &'db dyn Database) -> Ordering {
248        use GenericFunctionId::*;
249        let (a, b) = (&self.long(db).function, &other.long(db).function);
250        let title = |function: &GenericFunctionId<'db>| match function {
251            Free(id) => (FunctionTitleId::Free(*id), 0),
252            Extern(id) => (FunctionTitleId::Extern(*id), 1),
253            Impl(id) => (FunctionTitleId::Trait(id.function), 2),
254        };
255        let ((a_title, a_kind), (b_title, b_kind)) =
256            (title(&a.generic_function), title(&b.generic_function));
257        cmp_elements(db, &a_title, &b_title)
258            .then_with(|| a_kind.cmp(&b_kind))
259            .then_with(|| match (&a.generic_function, &b.generic_function) {
260                (Impl(a), Impl(b)) => a.impl_id.stable_cmp(&b.impl_id, db),
261                _ => Ordering::Equal,
262            })
263            .then_with(|| cmp_slices(db, &a.generic_args, &b.generic_args))
264    }
265}
266
267/// Ordered like `FunctionId`, by the impl function's definition where it has one.
268impl<'db> StableOrd<'db> for ConcreteFunctionWithBodyId<'db> {
269    fn stable_cmp(&self, other: &Self, db: &'db dyn Database) -> Ordering {
270        use GenericFunctionWithBodyId::*;
271        let (a, b) = (self.long(db), other.long(db));
272        let kind = |function: &GenericFunctionWithBodyId<'db>| match function {
273            Free(_) => 0,
274            Impl(_) => 1,
275            Trait(_) => 2,
276        };
277        cmp_elements(db, &a.function_with_body_id(db), &b.function_with_body_id(db))
278            .then_with(|| kind(&a.generic_function).cmp(&kind(&b.generic_function)))
279            .then_with(|| match (&a.generic_function, &b.generic_function) {
280                (Impl(a), Impl(b)) => {
281                    let (a, b) = (a.concrete_impl_id.long(db), b.concrete_impl_id.long(db));
282                    cmp_concrete(
283                        db,
284                        (&a.impl_def_id, &a.generic_args),
285                        (&b.impl_def_id, &b.generic_args),
286                    )
287                }
288                (Trait(a), Trait(b)) => a.concrete_trait(db).stable_cmp(&b.concrete_trait(db), db),
289                _ => Ordering::Equal,
290            })
291            .then_with(|| cmp_slices(db, &a.generic_args, &b.generic_args))
292    }
293}
294
295impl<'db> StableOrd<'db> for UninferredImpl<'db> {
296    fn stable_cmp(&self, other: &Self, db: &'db dyn Database) -> Ordering {
297        use UninferredImpl::*;
298        let kind = |imp: &Self| match imp {
299            Def(_) => 0,
300            ImplAlias(_) => 1,
301            GenericParam(_) => 2,
302            ImplImpl(_) => 3,
303            GeneratedImpl(_) => 4,
304        };
305        match (self, other) {
306            (Def(a), Def(b)) => cmp_elements(db, a, b),
307            (ImplAlias(a), ImplAlias(b)) => cmp_elements(db, a, b),
308            (GenericParam(a), GenericParam(b)) => cmp_elements(db, a, b),
309            (ImplImpl(a), ImplImpl(b)) => a
310                .impl_id()
311                .stable_cmp(&b.impl_id(), db)
312                .then_with(|| cmp_elements(db, &a.trait_impl_id(), &b.trait_impl_id())),
313            (GeneratedImpl(a), GeneratedImpl(b)) => {
314                let (a, b) = (a.long(db), b.long(db));
315                a.concrete_trait
316                    .stable_cmp(&b.concrete_trait, db)
317                    .then_with(|| a.impl_items.stable_cmp(&b.impl_items, db))
318            }
319            (a, b) => kind(a).cmp(&kind(b)),
320        }
321    }
322}