Skip to main content

nickel_lang_parser/ast/
combine.rs

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