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}