nickel_lang_core/bytecode/ast/
combine.rs

1//! Define a `Combine` trait that takes an allocator, two AST components, and returns a new AST
2//! component.
3
4use crate::eval::merge::merge_doc;
5
6use super::{
7    record::{FieldMetadata, MergePriority},
8    Annotation, AstAlloc,
9};
10
11/// Trait for structures representing a series of annotation that can be combined (flattened).
12/// Pedantically, `Combine` is a monoid: we expect that combining with `Default::default()` leaves
13/// the other value unchanged.
14pub trait Combine<'ast>: Default {
15    /// Combine two elements.
16    fn combine(alloc: &'ast AstAlloc, left: Self, right: Self) -> Self;
17}
18
19impl<'ast> Combine<'ast> for FieldMetadata<'ast> {
20    /// Combine two field metadata into one. If data that can't be combined (typically, the
21    /// documentation or the type annotation) are set by both, the left one's are kept.
22    fn combine(alloc: &'ast AstAlloc, left: Self, right: Self) -> Self {
23        let priority = match (left.priority, right.priority) {
24            // Neutral corresponds to the case where no priority was specified. In that case, the
25            // other priority takes precedence.
26            (MergePriority::Neutral, p) | (p, MergePriority::Neutral) => p,
27            // Otherwise, we keep the maximum of both priorities, as we would do when merging
28            // values.
29            (p1, p2) => std::cmp::max(p1, p2),
30        };
31
32        FieldMetadata {
33            doc: merge_doc(left.doc, right.doc),
34            annotation: Combine::combine(alloc, left.annotation, right.annotation),
35            opt: left.opt || right.opt,
36            // The resulting field will be suppressed from serialization if either of the fields to be merged is.
37            not_exported: left.not_exported || right.not_exported,
38            priority,
39        }
40    }
41}
42
43impl<'ast> Combine<'ast> for Annotation<'ast> {
44    /// Combine two annotations. If both have `types` set, the final type
45    /// is the one of the left annotation, while the right one's type is put
46    /// inside the final `contracts`.
47    ///
48    /// Contracts are combined from left to right; the left one's are put first,
49    /// then maybe the right one's type annotation and then the right one's
50    /// contracts.
51    fn combine(alloc: &'ast AstAlloc, left: Self, right: Self) -> Self {
52        let (typ, leftover) = match (left.typ, right.typ) {
53            (left_ty @ Some(_), right_ty @ Some(_)) => (left_ty, right_ty),
54            (left_ty, right_ty) => (left_ty.or(right_ty), None),
55        };
56
57        let contracts: Vec<_> = left
58            .contracts
59            .iter()
60            .cloned()
61            .chain(leftover)
62            .chain(right.contracts.iter().cloned())
63            .collect();
64
65        alloc.annotation(typ, contracts)
66    }
67}