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}