nickel_lang_core/bytecode/ast/
compat.rs

1//! Compatibility with the current stable Nickel version.
2//!
3//! This module defines a trait for converting to and from the representation used in stable Nickel
4//! to the new AST representation of the bytecode compiler, and implements it for the types defined
5//! in [crate::bytecode::ast].
6
7use super::{primop::PrimOp, *};
8use crate::{combine::Combine, label, position::RawSpan, term, typ as mline_type};
9use indexmap::IndexMap;
10use smallvec::SmallVec;
11
12/// Convert from the mainline Nickel representation to the new AST representation. This trait is
13/// mostly `From` with an additional argument for the allocator.
14///
15/// # Parameters
16///
17/// - `'ast`: the lifetime of the AST nodes, tied to the allocator
18/// - `'a`: the lifetime of the reference to the mainline Nickel object, which doesn't need to be
19///   related to `'ast` (we will copy any required data into the allocator)
20/// - `T`: the type of the mainline Nickel object ([term::Term], [term::pattern::Pattern], etc.)
21pub trait FromMainline<'ast, T> {
22    fn from_mainline(alloc: &'ast AstAlloc, mainline: &T) -> Self;
23}
24
25impl<'ast> FromMainline<'ast, term::pattern::Pattern> for &'ast Pattern<'ast> {
26    fn from_mainline(
27        alloc: &'ast AstAlloc,
28        pattern: &term::pattern::Pattern,
29    ) -> &'ast Pattern<'ast> {
30        alloc.alloc(pattern.to_ast(alloc))
31    }
32}
33
34impl<'ast> FromMainline<'ast, term::pattern::Pattern> for Pattern<'ast> {
35    fn from_mainline(alloc: &'ast AstAlloc, pattern: &term::pattern::Pattern) -> Self {
36        Pattern {
37            data: pattern.data.to_ast(alloc),
38            alias: pattern.alias,
39            pos: pattern.pos,
40        }
41    }
42}
43
44impl<'ast> FromMainline<'ast, term::pattern::PatternData> for PatternData<'ast> {
45    fn from_mainline(alloc: &'ast AstAlloc, data: &term::pattern::PatternData) -> Self {
46        match data {
47            term::pattern::PatternData::Wildcard => PatternData::Wildcard,
48            term::pattern::PatternData::Any(id) => PatternData::Any(*id),
49            term::pattern::PatternData::Record(record_pattern) => record_pattern.to_ast(alloc),
50            term::pattern::PatternData::Array(array_pattern) => array_pattern.to_ast(alloc),
51            term::pattern::PatternData::Enum(enum_pattern) => enum_pattern.to_ast(alloc),
52            term::pattern::PatternData::Constant(constant_pattern) => {
53                constant_pattern.to_ast(alloc)
54            }
55            term::pattern::PatternData::Or(or_pattern) => or_pattern.to_ast(alloc),
56        }
57    }
58}
59
60impl<'ast> FromMainline<'ast, term::pattern::RecordPattern> for PatternData<'ast> {
61    fn from_mainline(alloc: &'ast AstAlloc, record_pat: &term::pattern::RecordPattern) -> Self {
62        let patterns = record_pat
63            .patterns
64            .iter()
65            .map(|field_pattern| field_pattern.to_ast(alloc));
66
67        let tail = match record_pat.tail {
68            term::pattern::TailPattern::Empty => TailPattern::Empty,
69            term::pattern::TailPattern::Open => TailPattern::Open,
70            term::pattern::TailPattern::Capture(id) => TailPattern::Capture(id),
71        };
72
73        PatternData::Record(alloc.record_pattern(patterns, tail, record_pat.pos))
74    }
75}
76
77impl<'ast> FromMainline<'ast, term::pattern::FieldPattern> for FieldPattern<'ast> {
78    fn from_mainline(alloc: &'ast AstAlloc, field_pat: &term::pattern::FieldPattern) -> Self {
79        let pattern = field_pat.pattern.to_ast(alloc);
80
81        let default = field_pat.default.as_ref().map(|term| term.to_ast(alloc));
82
83        let annotation = field_pat.annotation.to_ast(alloc);
84
85        FieldPattern {
86            matched_id: field_pat.matched_id,
87            annotation,
88            default,
89            pattern,
90            pos: field_pat.pos,
91        }
92    }
93}
94
95impl<'ast> FromMainline<'ast, term::pattern::ArrayPattern> for PatternData<'ast> {
96    fn from_mainline(alloc: &'ast AstAlloc, array_pat: &term::pattern::ArrayPattern) -> Self {
97        let patterns = array_pat.patterns.iter().map(|pat| pat.to_ast(alloc));
98
99        let tail = match array_pat.tail {
100            term::pattern::TailPattern::Empty => TailPattern::Empty,
101            term::pattern::TailPattern::Open => TailPattern::Open,
102            term::pattern::TailPattern::Capture(id) => TailPattern::Capture(id),
103        };
104
105        PatternData::Array(alloc.array_pattern(patterns, tail, array_pat.pos))
106    }
107}
108
109impl<'ast> FromMainline<'ast, term::pattern::EnumPattern> for PatternData<'ast> {
110    fn from_mainline(alloc: &'ast AstAlloc, enum_pat: &term::pattern::EnumPattern) -> Self {
111        let pattern = enum_pat.pattern.as_ref().map(|pat| (**pat).to_ast(alloc));
112        PatternData::Enum(alloc.alloc(EnumPattern {
113            tag: enum_pat.tag,
114            pattern,
115            pos: enum_pat.pos,
116        }))
117    }
118}
119
120impl<'ast> FromMainline<'ast, term::pattern::ConstantPattern> for PatternData<'ast> {
121    fn from_mainline(alloc: &'ast AstAlloc, pattern: &term::pattern::ConstantPattern) -> Self {
122        let data = match &pattern.data {
123            term::pattern::ConstantPatternData::Bool(b) => ConstantPatternData::Bool(*b),
124            term::pattern::ConstantPatternData::Number(n) => {
125                ConstantPatternData::Number(alloc.alloc_number(n.clone()))
126            }
127            term::pattern::ConstantPatternData::String(s) => {
128                ConstantPatternData::String(alloc.alloc_str(s))
129            }
130            term::pattern::ConstantPatternData::Null => ConstantPatternData::Null,
131        };
132
133        PatternData::Constant(alloc.alloc(ConstantPattern {
134            data,
135            pos: pattern.pos,
136        }))
137    }
138}
139
140impl<'ast> FromMainline<'ast, term::pattern::OrPattern> for PatternData<'ast> {
141    fn from_mainline(alloc: &'ast AstAlloc, pattern: &term::pattern::OrPattern) -> Self {
142        let patterns = pattern
143            .patterns
144            .iter()
145            .map(|pat| pat.to_ast(alloc))
146            .collect::<Vec<_>>();
147
148        PatternData::Or(alloc.or_pattern(patterns, pattern.pos))
149    }
150}
151
152impl<'ast> FromMainline<'ast, term::TypeAnnotation> for Annotation<'ast> {
153    fn from_mainline(alloc: &'ast AstAlloc, annot: &term::TypeAnnotation) -> Self {
154        let typ = annot.typ.as_ref().map(|typ| typ.typ.to_ast(alloc));
155
156        let contracts = alloc.alloc_many(
157            annot
158                .contracts
159                .iter()
160                .map(|contract| contract.typ.to_ast(alloc)),
161        );
162
163        Annotation { typ, contracts }
164    }
165}
166
167impl<'ast> FromMainline<'ast, (LocIdent, term::record::Field)> for record::FieldDef<'ast> {
168    fn from_mainline(
169        alloc: &'ast AstAlloc,
170        (id, content): &(LocIdent, term::record::Field),
171    ) -> Self {
172        let pos = content
173            .value
174            .as_ref()
175            .map(|value| id.pos.fuse(value.pos))
176            .unwrap_or(id.pos);
177
178        record::FieldDef {
179            path: alloc.alloc_singleton(record::FieldPathElem::Ident(*id)),
180            value: content.value.as_ref().map(|term| term.to_ast(alloc)),
181            metadata: content.metadata.to_ast(alloc),
182            pos,
183        }
184    }
185}
186
187impl<'ast> FromMainline<'ast, term::record::FieldMetadata> for record::FieldMetadata<'ast> {
188    fn from_mainline(alloc: &'ast AstAlloc, metadata: &term::record::FieldMetadata) -> Self {
189        let doc = metadata.doc.as_ref().map(|doc| alloc.alloc_str(doc));
190
191        record::FieldMetadata {
192            doc,
193            annotation: metadata.annotation.to_ast(alloc),
194            opt: metadata.opt,
195            not_exported: metadata.not_exported,
196            priority: metadata.priority.clone(),
197        }
198    }
199}
200
201impl<'ast> FromMainline<'ast, term::record::Include> for record::Include<'ast> {
202    fn from_mainline(alloc: &'ast AstAlloc, mainline: &term::record::Include) -> Self {
203        record::Include {
204            ident: mainline.ident,
205            metadata: mainline.metadata.to_ast(alloc),
206        }
207    }
208}
209
210impl<'ast> FromMainline<'ast, mline_type::Type> for Type<'ast> {
211    fn from_mainline(alloc: &'ast AstAlloc, mainline: &mline_type::Type) -> Self {
212        Type {
213            typ: mainline.typ.to_ast(alloc),
214            pos: mainline.pos,
215        }
216    }
217}
218
219type MainlineTypeUnr = mline_type::TypeF<
220    Box<mline_type::Type>,
221    mline_type::RecordRows,
222    mline_type::EnumRows,
223    term::RichTerm,
224>;
225
226impl<'ast> FromMainline<'ast, MainlineTypeUnr> for TypeUnr<'ast> {
227    fn from_mainline(alloc: &'ast AstAlloc, typ: &MainlineTypeUnr) -> Self {
228        typ.clone().map(
229            |typ| alloc.alloc((*typ).to_ast(alloc)),
230            |rrows| rrows.to_ast(alloc),
231            |erows| erows.to_ast(alloc),
232            |ctr| ctr.to_ast(alloc),
233        )
234    }
235}
236
237impl<'ast> FromMainline<'ast, mline_type::RecordRows> for RecordRows<'ast> {
238    fn from_mainline(alloc: &'ast AstAlloc, rrows: &mline_type::RecordRows) -> Self {
239        RecordRows(rrows.0.to_ast(alloc))
240    }
241}
242
243impl<'ast> FromMainline<'ast, mline_type::EnumRows> for EnumRows<'ast> {
244    fn from_mainline(alloc: &'ast AstAlloc, erows: &mline_type::EnumRows) -> Self {
245        EnumRows(erows.0.to_ast(alloc))
246    }
247}
248
249type MainlineEnumRowsUnr = mline_type::EnumRowsF<Box<mline_type::Type>, Box<mline_type::EnumRows>>;
250
251impl<'ast> FromMainline<'ast, MainlineEnumRowsUnr> for EnumRowsUnr<'ast> {
252    fn from_mainline(alloc: &'ast AstAlloc, erows: &MainlineEnumRowsUnr) -> Self {
253        erows.clone().map(
254            |typ| alloc.alloc((*typ).to_ast(alloc)),
255            |erows| alloc.alloc((*erows).to_ast(alloc)),
256        )
257    }
258}
259
260type MainlineRecordRowsUnr =
261    mline_type::RecordRowsF<Box<mline_type::Type>, Box<mline_type::RecordRows>>;
262
263impl<'ast> FromMainline<'ast, MainlineRecordRowsUnr> for RecordRowsUnr<'ast> {
264    fn from_mainline(alloc: &'ast AstAlloc, rrows: &MainlineRecordRowsUnr) -> Self {
265        rrows.clone().map(
266            |typ| alloc.alloc((*typ).to_ast(alloc)),
267            |rrows| alloc.alloc((*rrows).to_ast(alloc)),
268        )
269    }
270}
271
272impl<'ast> FromMainline<'ast, term::Term> for Node<'ast> {
273    fn from_mainline(alloc: &'ast AstAlloc, term: &term::Term) -> Self {
274        use term::Term;
275
276        match term {
277            Term::Null => Node::Null,
278            Term::Bool(b) => Node::Bool(*b),
279            Term::Num(n) => alloc.number(n.clone()),
280            Term::Str(s) => alloc.string(s),
281            Term::StrChunks(chunks) => {
282                alloc.string_chunks(chunks.iter().rev().map(|chunk| match chunk {
283                    term::StrChunk::Literal(s) => StringChunk::Literal(s.clone()),
284                    term::StrChunk::Expr(expr, indent) => {
285                        StringChunk::Expr(expr.to_ast(alloc), *indent)
286                    }
287                }))
288            }
289            t @ (Term::Fun(_, body) | Term::FunPattern(_, body)) => {
290                let fst_arg = match t {
291                    Term::Fun(id, _) => Pattern::any(*id),
292                    Term::FunPattern(pat, _) => pat.to_ast(alloc),
293                    // unreachable!(): we are in a match arm that matches either Fun or FunPattern
294                    _ => unreachable!(),
295                };
296
297                let mut args = vec![fst_arg];
298                let mut maybe_next_fun = body;
299
300                let final_body = loop {
301                    match maybe_next_fun.as_ref() {
302                        Term::Fun(next_id, next_body) => {
303                            args.push(Pattern::any(*next_id));
304                            maybe_next_fun = next_body;
305                        }
306                        Term::FunPattern(next_pat, next_body) => {
307                            args.push(next_pat.to_ast(alloc));
308                            maybe_next_fun = next_body;
309                        }
310                        _ => break maybe_next_fun,
311                    }
312                };
313
314                alloc.fun(args, final_body.to_ast(alloc))
315            }
316            Term::Let(bindings, body, attrs) => alloc.let_block(
317                bindings.iter().map(|(id, value)| LetBinding {
318                    pattern: Pattern::any(*id),
319                    value: value.to_ast(alloc),
320                    metadata: Default::default(),
321                }),
322                body.to_ast(alloc),
323                attrs.rec,
324            ),
325            Term::LetPattern(bindings, body, attrs) => alloc.let_block(
326                bindings.iter().map(|(pat, value)| LetBinding {
327                    pattern: pat.to_ast(alloc),
328                    value: value.to_ast(alloc),
329                    metadata: Default::default(),
330                }),
331                body.to_ast(alloc),
332                attrs.rec,
333            ),
334            Term::App(fun, arg) => {
335                match fun.as_ref() {
336                    // We have to special-case if-then-else, which is encoded as a primop application
337                    // of the unary operator if-then-else to the condition, followed by two normal
338                    // applications for the if and else branches, which is a bit painful to match.
339                    Term::App(fun_inner, arg_inner)
340                        if matches!(
341                            fun_inner.as_ref(),
342                            Term::Op1(term::UnaryOp::IfThenElse, _)
343                        ) =>
344                    {
345                        if let Term::Op1(term::UnaryOp::IfThenElse, cond) = fun_inner.as_ref() {
346                            return alloc.if_then_else(
347                                cond.to_ast(alloc),
348                                arg_inner.to_ast(alloc),
349                                arg.to_ast(alloc),
350                            );
351                        }
352                    }
353                    _ => (),
354                };
355
356                let mut args = vec![arg.to_ast(alloc)];
357                let mut maybe_next_app = fun.as_ref();
358
359                while let Term::App(next_fun, next_arg) = maybe_next_app {
360                    args.push(next_arg.to_ast(alloc));
361                    maybe_next_app = next_fun.as_ref();
362                }
363
364                // Application is left-associative: `f x y` is parsed as `(f x) y`. So we see the
365                // outer argument `y` first, and `args` will be `[y, x]`. We need to reverse it to
366                // match the expected order of a flat application node.
367                args.reverse();
368                alloc.app(fun.to_ast(alloc), args)
369            }
370            Term::Var(id) => Node::Var(*id),
371            Term::Enum(id) => alloc.enum_variant(*id, None),
372            Term::EnumVariant { tag, arg, attrs: _ } => {
373                alloc.enum_variant(*tag, Some(arg.to_ast(alloc)))
374            }
375            Term::RecRecord(data, includes, dyn_fields, _deps) => {
376                let mut field_defs = Vec::new();
377
378                field_defs.extend(data.fields.iter().map(|(id, field)| {
379                    let pos = field
380                        .value
381                        .as_ref()
382                        .map(|value| id.pos.fuse(value.pos))
383                        .unwrap_or(id.pos);
384
385                    record::FieldDef {
386                        path: record::FieldPathElem::single_ident_path(alloc, *id),
387                        value: field.value.as_ref().map(|term| term.to_ast(alloc)),
388                        metadata: field.metadata.to_ast(alloc),
389                        pos,
390                    }
391                }));
392
393                field_defs.extend(dyn_fields.iter().map(|(expr, field)| {
394                    let pos_field_name = expr.pos;
395                    let pos = field
396                        .value
397                        .as_ref()
398                        .map(|v| pos_field_name.fuse(v.pos))
399                        .unwrap_or(pos_field_name);
400
401                    record::FieldDef {
402                        path: record::FieldPathElem::single_expr_path(alloc, expr.to_ast(alloc)),
403                        metadata: field.metadata.to_ast(alloc),
404                        value: field.value.as_ref().map(|term| term.to_ast(alloc)),
405                        pos,
406                    }
407                }));
408
409                alloc.record(Record {
410                    field_defs: alloc.alloc_many(field_defs),
411                    includes: alloc.alloc_many(includes.iter().map(|incl| incl.to_ast(alloc))),
412                    open: data.attrs.open,
413                })
414            }
415            Term::Record(data) => {
416                let field_defs = alloc.alloc_many(data.fields.iter().map(|(id, field)| {
417                    let pos = field
418                        .value
419                        .as_ref()
420                        .map(|value| id.pos.fuse(value.pos))
421                        .unwrap_or(id.pos);
422
423                    record::FieldDef {
424                        path: record::FieldPathElem::single_ident_path(alloc, *id),
425                        value: field.value.as_ref().map(|term| term.to_ast(alloc)),
426                        metadata: field.metadata.to_ast(alloc),
427                        pos,
428                    }
429                }));
430
431                alloc.record(Record {
432                    field_defs,
433                    includes: &[],
434                    open: data.attrs.open,
435                })
436            }
437            Term::Match(data) => {
438                let branches = data.branches.iter().map(|branch| MatchBranch {
439                    pattern: branch.pattern.to_ast(alloc),
440                    guard: branch.guard.as_ref().map(|term| term.to_ast(alloc)),
441                    body: branch.body.to_ast(alloc),
442                });
443
444                alloc.match_expr(branches)
445            }
446            Term::Array(data, _attrs) => {
447                // We should probably make array's iterator an ExactSizeIterator. But for now, we
448                // don't care about the translation's performance so it's simpler to just collect
449                // them in a vec locally.
450                let elts = data
451                    .iter()
452                    .map(|term| term.to_ast(alloc))
453                    .collect::<Vec<_>>();
454                alloc.array(elts)
455            }
456            Term::Op1(op, arg) => {
457                alloc.prim_op(PrimOp::from(op), std::iter::once(arg.to_ast(alloc)))
458            }
459            Term::Op2(op, arg1, arg2) => {
460                // [^primop-argument-order]: Some primops have had exotic arguments order for
461                // historical reasons. The new AST tries to follow the stdlib argument order
462                // whenever possible, which means we have to swap the arguments for a few primops.
463
464                let op = PrimOp::from(op);
465                let mut args = [arg1.to_ast(alloc), arg2.to_ast(alloc)];
466
467                if matches!(op, PrimOp::ArrayAt | PrimOp::StringContains) {
468                    args.swap(0, 1);
469                }
470
471                alloc.prim_op(op, args)
472            }
473            Term::OpN(op, args) => {
474                // See [^primop-argument-order].
475                let op = PrimOp::from(op);
476                let mut args: Vec<_> = args.iter().map(|arg| arg.to_ast(alloc)).collect();
477                if let PrimOp::StringSubstr = op {
478                    debug_assert_eq!(args.len(), 3);
479                    // The original order is: the string, then start and end.
480                    // The target order is: start, end and then the string.
481                    args.swap(0, 1);
482                    args.swap(1, 2);
483                }
484
485                alloc.prim_op(op, args)
486            }
487            Term::SealingKey(_) => panic!("didn't expect a sealing key at the first stage"),
488            Term::Sealed(..) => panic!("didn't expect a sealed term at the first stage"),
489            Term::Annotated(annot, term) => {
490                alloc.annotated(annot.to_ast(alloc), term.to_ast(alloc))
491            }
492            Term::Import(term::Import::Path { path, format }) => {
493                alloc.import_path(path.clone(), *format)
494            }
495            Term::Import(term::Import::Package { id }) => alloc.import_package(*id),
496            Term::ResolvedImport(_) => panic!("didn't expect a resolved import at parsing stage"),
497            Term::Type { typ, .. } => alloc.typ(typ.to_ast(alloc)),
498            Term::CustomContract(_) => panic!("didn't expect a custom contract at parsing stage"),
499            Term::ParseError(error) => alloc.parse_error(error.clone()),
500            Term::RuntimeError(_) => panic!("didn't expect a runtime error at parsing stage"),
501            Term::Closure(_) => panic!("didn't expect a closure at parsing stage"),
502            Term::ForeignId(_) => panic!("didn't expect a foreign id at parsing stage"),
503            _ => unimplemented!(),
504        }
505    }
506}
507
508impl<'ast> FromMainline<'ast, term::RichTerm> for Ast<'ast> {
509    fn from_mainline(alloc: &'ast AstAlloc, rterm: &term::RichTerm) -> Self {
510        Ast {
511            node: rterm.as_ref().to_ast(alloc),
512            pos: rterm.pos,
513        }
514    }
515}
516
517impl<'ast> FromMainline<'ast, term::RichTerm> for &'ast Ast<'ast> {
518    fn from_mainline(alloc: &'ast AstAlloc, rterm: &term::RichTerm) -> Self {
519        alloc.alloc(rterm.to_ast(alloc))
520    }
521}
522
523/// Symmetric to `FromMainline`, as `Into` is to `From`.
524pub trait ToAst<'ast, T> {
525    fn to_ast(&self, alloc: &'ast AstAlloc) -> T;
526}
527
528impl<'ast, S, T> ToAst<'ast, T> for S
529where
530    T: FromMainline<'ast, S>,
531{
532    fn to_ast(&self, alloc: &'ast AstAlloc) -> T {
533        T::from_mainline(alloc, self)
534    }
535}
536
537// Primops don't need any heap allocation, so we can implement `From` directly.
538impl From<&term::UnaryOp> for PrimOp {
539    fn from(op: &term::UnaryOp) -> Self {
540        match op {
541            term::UnaryOp::IfThenElse => {
542                panic!("if-then-else should have been handed separately by special casing")
543            }
544            term::UnaryOp::Typeof => PrimOp::Typeof,
545            term::UnaryOp::Cast => PrimOp::Cast,
546            term::UnaryOp::BoolAnd => PrimOp::BoolAnd,
547            term::UnaryOp::BoolOr => PrimOp::BoolOr,
548            term::UnaryOp::BoolNot => PrimOp::BoolNot,
549            term::UnaryOp::Blame => PrimOp::Blame,
550            term::UnaryOp::EnumEmbed(loc_ident) => PrimOp::EnumEmbed(*loc_ident),
551            term::UnaryOp::RecordAccess(loc_ident) => PrimOp::RecordStatAccess(*loc_ident),
552            term::UnaryOp::ArrayMap => PrimOp::ArrayMap,
553            term::UnaryOp::RecordMap => PrimOp::RecordMap,
554            term::UnaryOp::LabelFlipPol => PrimOp::LabelFlipPol,
555            term::UnaryOp::LabelPol => PrimOp::LabelPol,
556            term::UnaryOp::LabelGoDom => PrimOp::LabelGoDom,
557            term::UnaryOp::LabelGoCodom => PrimOp::LabelGoCodom,
558            term::UnaryOp::LabelGoArray => PrimOp::LabelGoArray,
559            term::UnaryOp::LabelGoDict => PrimOp::LabelGoDict,
560            term::UnaryOp::Seq => PrimOp::Seq,
561            term::UnaryOp::DeepSeq => PrimOp::DeepSeq,
562            term::UnaryOp::ArrayLength => PrimOp::ArrayLength,
563            term::UnaryOp::ArrayGen => PrimOp::ArrayGen,
564            term::UnaryOp::RecordFields(record_op_kind) => PrimOp::RecordFields(*record_op_kind),
565            term::UnaryOp::RecordValues => PrimOp::RecordValues,
566            term::UnaryOp::StringTrim => PrimOp::StringTrim,
567            term::UnaryOp::StringChars => PrimOp::StringChars,
568            term::UnaryOp::StringUppercase => PrimOp::StringUppercase,
569            term::UnaryOp::StringLowercase => PrimOp::StringLowercase,
570            term::UnaryOp::StringLength => PrimOp::StringLength,
571            term::UnaryOp::ToString => PrimOp::ToString,
572            term::UnaryOp::NumberFromString => PrimOp::NumberFromString,
573            term::UnaryOp::EnumFromString => PrimOp::EnumFromString,
574            term::UnaryOp::StringIsMatch => PrimOp::StringIsMatch,
575            term::UnaryOp::StringFind => PrimOp::StringFind,
576            term::UnaryOp::StringFindAll => PrimOp::StringFindAll,
577            term::UnaryOp::Force {
578                ignore_not_exported,
579            } => PrimOp::Force {
580                ignore_not_exported: *ignore_not_exported,
581            },
582            term::UnaryOp::RecordEmptyWithTail => PrimOp::RecordEmptyWithTail,
583            term::UnaryOp::RecordFreeze => PrimOp::RecordFreeze,
584            term::UnaryOp::Trace => PrimOp::Trace,
585            term::UnaryOp::LabelPushDiag => PrimOp::LabelPushDiag,
586            #[cfg(feature = "nix-experimental")]
587            term::UnaryOp::EvalNix => PrimOp::EvalNix,
588            term::UnaryOp::EnumGetArg => PrimOp::EnumGetArg,
589            term::UnaryOp::EnumMakeVariant => PrimOp::EnumMakeVariant,
590            term::UnaryOp::EnumIsVariant => PrimOp::EnumIsVariant,
591            term::UnaryOp::EnumGetTag => PrimOp::EnumGetTag,
592            term::UnaryOp::ContractCustom => PrimOp::ContractCustom,
593            term::UnaryOp::NumberArcCos => PrimOp::NumberArcCos,
594            term::UnaryOp::NumberArcSin => PrimOp::NumberArcSin,
595            term::UnaryOp::NumberArcTan => PrimOp::NumberArcTan,
596            term::UnaryOp::NumberCos => PrimOp::NumberCos,
597            term::UnaryOp::NumberSin => PrimOp::NumberSin,
598            term::UnaryOp::NumberTan => PrimOp::NumberTan,
599
600            op @ (term::UnaryOp::TagsOnlyMatch { .. }
601            | term::UnaryOp::ChunksConcat
602            | term::UnaryOp::StringIsMatchCompiled(_)
603            | term::UnaryOp::StringFindCompiled(_)
604            | term::UnaryOp::StringFindAllCompiled(_)
605            | term::UnaryOp::RecDefault
606            | term::UnaryOp::RecForce
607            | term::UnaryOp::PatternBranch
608            | term::UnaryOp::ContractPostprocessResult
609            | term::UnaryOp::ContractAttachDefaultLabel) => {
610                panic!("didn't expect {op} at the parsing stage")
611            }
612        }
613    }
614}
615
616impl From<&term::BinaryOp> for PrimOp {
617    fn from(op: &term::BinaryOp) -> Self {
618        match op {
619            term::BinaryOp::Plus => PrimOp::Plus,
620            term::BinaryOp::Sub => PrimOp::Sub,
621            term::BinaryOp::Mult => PrimOp::Mult,
622            term::BinaryOp::Div => PrimOp::Div,
623            term::BinaryOp::Modulo => PrimOp::Modulo,
624            term::BinaryOp::NumberArcTan2 => PrimOp::NumberArcTan2,
625            term::BinaryOp::NumberLog => PrimOp::NumberLog,
626            term::BinaryOp::Pow => PrimOp::Pow,
627            term::BinaryOp::StringConcat => PrimOp::StringConcat,
628            term::BinaryOp::Eq => PrimOp::Eq,
629            term::BinaryOp::LessThan => PrimOp::LessThan,
630            term::BinaryOp::LessOrEq => PrimOp::LessOrEq,
631            term::BinaryOp::GreaterThan => PrimOp::GreaterThan,
632            term::BinaryOp::GreaterOrEq => PrimOp::GreaterOrEq,
633            term::BinaryOp::ContractApply => PrimOp::ContractApply,
634            term::BinaryOp::ContractCheck => PrimOp::ContractCheck,
635            term::BinaryOp::LabelWithErrorData => PrimOp::LabelWithErrorData,
636            term::BinaryOp::LabelGoField => PrimOp::LabelGoField,
637            // This corresponds to a call to `%record/insert%` from the source language. Other
638            // forms are introduced by the evaluator, e.g. when evaluating a recursive record to a
639            // record.
640            term::BinaryOp::RecordInsert {
641                metadata,
642                pending_contracts,
643                ext_kind,
644                op_kind,
645            } if metadata.is_empty()
646                && pending_contracts.is_empty()
647                && *ext_kind == term::RecordExtKind::WithValue =>
648            {
649                PrimOp::RecordInsert(*op_kind)
650            }
651            term::BinaryOp::RecordRemove(record_op_kind) => PrimOp::RecordRemove(*record_op_kind),
652            term::BinaryOp::RecordGet => PrimOp::RecordGet,
653            term::BinaryOp::RecordHasField(record_op_kind) => {
654                PrimOp::RecordHasField(*record_op_kind)
655            }
656            term::BinaryOp::RecordFieldIsDefined(record_op_kind) => {
657                PrimOp::RecordFieldIsDefined(*record_op_kind)
658            }
659            term::BinaryOp::RecordSplitPair => PrimOp::RecordSplitPair,
660            term::BinaryOp::RecordDisjointMerge => PrimOp::RecordDisjointMerge,
661            term::BinaryOp::ArrayConcat => PrimOp::ArrayConcat,
662            term::BinaryOp::ArrayAt => PrimOp::ArrayAt,
663            term::BinaryOp::Merge(merge_label) => PrimOp::Merge(merge_label.kind),
664            term::BinaryOp::Hash => PrimOp::Hash,
665            term::BinaryOp::Serialize => PrimOp::Serialize,
666            term::BinaryOp::Deserialize => PrimOp::Deserialize,
667            term::BinaryOp::StringSplit => PrimOp::StringSplit,
668            term::BinaryOp::StringContains => PrimOp::StringContains,
669            term::BinaryOp::StringCompare => PrimOp::StringCompare,
670            term::BinaryOp::ContractArrayLazyApp => PrimOp::ContractArrayLazyApp,
671            term::BinaryOp::ContractRecordLazyApp => PrimOp::ContractRecordLazyApp,
672            term::BinaryOp::LabelWithMessage => PrimOp::LabelWithMessage,
673            term::BinaryOp::LabelWithNotes => PrimOp::LabelWithNotes,
674            term::BinaryOp::LabelAppendNote => PrimOp::LabelAppendNote,
675            term::BinaryOp::LabelLookupTypeVar => PrimOp::LabelLookupTypeVar,
676
677            term::BinaryOp::Seal => PrimOp::Seal,
678            term::BinaryOp::Unseal => PrimOp::Unseal,
679
680            term::BinaryOp::RecordInsert { .. } => {
681                panic!("didn't expect %record/insert% at the parsing stage")
682            }
683        }
684    }
685}
686
687impl From<&term::NAryOp> for PrimOp {
688    fn from(op: &term::NAryOp) -> Self {
689        match op {
690            term::NAryOp::StringReplace => PrimOp::StringReplace,
691            term::NAryOp::StringReplaceRegex => PrimOp::StringReplaceRegex,
692            term::NAryOp::StringSubstr => PrimOp::StringSubstr,
693            term::NAryOp::MergeContract => PrimOp::MergeContract,
694            term::NAryOp::RecordSealTail => PrimOp::RecordSealTail,
695            term::NAryOp::RecordUnsealTail => PrimOp::RecordUnsealTail,
696            term::NAryOp::LabelInsertTypeVar => PrimOp::LabelInsertTypeVar,
697            term::NAryOp::ArraySlice => PrimOp::ArraySlice,
698        }
699    }
700}
701
702/// Trait from converting from the new AST representation to the mainline Nickel representation.
703///
704/// Note that in that direction, we don't need the allocator: those traits are thus isomorphic to
705/// to `From<_>` and `Into<_>` respectively. However, we convert from a reference to an owned
706/// value. We initially used `From` directly, but this causes annoying inference issue around auto
707/// deref and blanket implementations of `From`/`Into`. It's just simpler and more explicit to have
708/// a separate trait for this conversion as well.
709pub trait FromAst<T> {
710    fn from_ast(ast: &T) -> Self;
711}
712
713pub trait ToMainline<T> {
714    fn to_mainline(&self) -> T;
715}
716
717impl<S, T> ToMainline<T> for S
718where
719    T: FromAst<S>,
720{
721    fn to_mainline(&self) -> T {
722        T::from_ast(self)
723    }
724}
725
726impl<'ast> FromAst<Pattern<'ast>> for term::pattern::Pattern {
727    fn from_ast(pattern: &Pattern<'ast>) -> Self {
728        term::pattern::Pattern {
729            data: pattern.data.to_mainline(),
730            alias: pattern.alias,
731            pos: pattern.pos,
732        }
733    }
734}
735
736impl<'ast> FromAst<PatternData<'ast>> for term::pattern::PatternData {
737    fn from_ast(ast: &PatternData<'ast>) -> Self {
738        match ast {
739            PatternData::Wildcard => term::pattern::PatternData::Wildcard,
740            PatternData::Any(id) => term::pattern::PatternData::Any(*id),
741            PatternData::Record(record_pattern) => (*record_pattern).to_mainline(),
742            PatternData::Array(array_pattern) => (*array_pattern).to_mainline(),
743            PatternData::Enum(enum_pattern) => (*enum_pattern).to_mainline(),
744            PatternData::Constant(constant_pattern) => (*constant_pattern).to_mainline(),
745            PatternData::Or(or_pattern) => (*or_pattern).to_mainline(),
746        }
747    }
748}
749
750impl<'ast> FromAst<RecordPattern<'ast>> for term::pattern::PatternData {
751    fn from_ast(record_pat: &RecordPattern<'ast>) -> Self {
752        let patterns = record_pat
753            .patterns
754            .iter()
755            .map(|field_pattern| field_pattern.to_mainline())
756            .collect();
757
758        let tail = match record_pat.tail {
759            TailPattern::Empty => term::pattern::TailPattern::Empty,
760            TailPattern::Open => term::pattern::TailPattern::Open,
761            TailPattern::Capture(id) => term::pattern::TailPattern::Capture(id),
762        };
763
764        term::pattern::PatternData::Record(term::pattern::RecordPattern {
765            patterns,
766            tail,
767            pos: record_pat.pos,
768        })
769    }
770}
771
772impl<'ast> FromAst<FieldPattern<'ast>> for term::pattern::FieldPattern {
773    fn from_ast(field_pat: &FieldPattern<'ast>) -> Self {
774        let pattern = field_pat.pattern.to_mainline();
775
776        let default = field_pat.default.as_ref().map(|term| term.to_mainline());
777
778        let annotation = field_pat.annotation.to_mainline();
779
780        term::pattern::FieldPattern {
781            matched_id: field_pat.matched_id,
782            annotation,
783            default,
784            pattern,
785            pos: field_pat.pos,
786        }
787    }
788}
789
790impl<'ast> FromAst<ArrayPattern<'ast>> for term::pattern::PatternData {
791    fn from_ast(array_pat: &ArrayPattern<'ast>) -> Self {
792        let patterns = array_pat
793            .patterns
794            .iter()
795            .map(|pat| pat.to_mainline())
796            .collect();
797
798        let tail = match array_pat.tail {
799            TailPattern::Empty => term::pattern::TailPattern::Empty,
800            TailPattern::Open => term::pattern::TailPattern::Open,
801            TailPattern::Capture(id) => term::pattern::TailPattern::Capture(id),
802        };
803
804        term::pattern::PatternData::Array(term::pattern::ArrayPattern {
805            patterns,
806            tail,
807            pos: array_pat.pos,
808        })
809    }
810}
811
812impl<'ast> FromAst<EnumPattern<'ast>> for term::pattern::PatternData {
813    fn from_ast(enum_pat: &EnumPattern<'ast>) -> Self {
814        let pattern = enum_pat
815            .pattern
816            .as_ref()
817            .map(|pat| Box::new(pat.to_mainline()));
818
819        term::pattern::PatternData::Enum(term::pattern::EnumPattern {
820            tag: enum_pat.tag,
821            pattern,
822            pos: enum_pat.pos,
823        })
824    }
825}
826
827impl<'ast> FromAst<ConstantPattern<'ast>> for term::pattern::PatternData {
828    fn from_ast(pattern: &ConstantPattern<'ast>) -> Self {
829        let data = match pattern.data {
830            ConstantPatternData::Bool(b) => term::pattern::ConstantPatternData::Bool(b),
831            ConstantPatternData::Number(n) => term::pattern::ConstantPatternData::Number(n.clone()),
832            ConstantPatternData::String(s) => term::pattern::ConstantPatternData::String(s.into()),
833            ConstantPatternData::Null => term::pattern::ConstantPatternData::Null,
834        };
835
836        term::pattern::PatternData::Constant(term::pattern::ConstantPattern {
837            data,
838            pos: pattern.pos,
839        })
840    }
841}
842
843impl<'ast> FromAst<OrPattern<'ast>> for term::pattern::PatternData {
844    fn from_ast(pattern: &OrPattern<'ast>) -> Self {
845        let patterns = pattern
846            .patterns
847            .iter()
848            .map(|pat| pat.to_mainline())
849            .collect::<Vec<_>>();
850
851        term::pattern::PatternData::Or(term::pattern::OrPattern {
852            patterns,
853            pos: pattern.pos,
854        })
855    }
856}
857
858impl<'ast> FromAst<Annotation<'ast>> for term::TypeAnnotation {
859    fn from_ast(annot: &Annotation<'ast>) -> Self {
860        let typ = annot.typ.as_ref().map(ToMainline::to_mainline);
861
862        let contracts = annot
863            .contracts
864            .iter()
865            .map(ToMainline::to_mainline)
866            .collect();
867
868        term::TypeAnnotation { typ, contracts }
869    }
870}
871
872impl<'ast> FromAst<StringChunk<Ast<'ast>>> for term::StrChunk<term::RichTerm> {
873    fn from_ast(chunk: &StringChunk<Ast<'ast>>) -> Self {
874        match chunk {
875            StringChunk::Literal(s) => term::StrChunk::Literal(s.clone()),
876            StringChunk::Expr(expr, indent) => term::StrChunk::Expr(expr.to_mainline(), *indent),
877        }
878    }
879}
880
881/// Similar to [record::FieldPathElem], but for the mainline representation: either an identifier
882/// or a quoted identifier.
883pub enum FieldName {
884    Ident(LocIdent),
885    Expr(term::RichTerm),
886}
887
888impl FromAst<record::FieldPathElem<'_>> for FieldName {
889    fn from_ast(elem: &record::FieldPathElem<'_>) -> Self {
890        match elem {
891            record::FieldPathElem::Ident(id) => FieldName::Ident(*id),
892            record::FieldPathElem::Expr(node) => FieldName::Expr(node.to_mainline()),
893        }
894    }
895}
896
897impl<'ast> FromAst<record::FieldDef<'ast>> for (FieldName, term::record::Field) {
898    fn from_ast(field: &record::FieldDef<'ast>) -> Self {
899        /// Elaborate a record field definition specified as a path, like `a.b.c = foo`, into a regular
900        /// flat definition `a = {b = {c = foo}}`.
901        ///
902        /// # Preconditions
903        /// - /!\ path must be **non-empty**, otherwise this function panics
904        use super::record::FieldPathElem;
905
906        // unwrap(): field paths must be non-empty
907        let name_innermost = field.path.last().unwrap().try_as_ident();
908
909        let initial = term::record::Field {
910            value: field.value.as_ref().map(ToMainline::to_mainline),
911            metadata: term::record::FieldMetadata::from_ast(&field.metadata)
912                .with_field_name(name_innermost),
913            pending_contracts: Vec::new(),
914        };
915
916        let mut it = field.path.iter();
917        let fst = it.next().unwrap();
918
919        let content = it.rev().fold(initial, |acc, path_elem| {
920            // We first compute a position for the intermediate generated records (it's useful
921            // in particular for the LSP). The position starts at the subpath corresponding to
922            // the intermediate record and ends at the final value.
923            let acc_pos = acc.value.as_ref().map(|value| value.pos);
924
925            let pos = acc_pos
926                .map(|p| p.fuse(path_elem.pos()))
927                .unwrap_or_else(|| path_elem.pos());
928
929            match path_elem {
930                FieldPathElem::Ident(id) => {
931                    let mut fields = IndexMap::new();
932                    fields.insert(*id, acc);
933                    term::record::Field::from(term::RichTerm::new(
934                        term::Term::Record(term::record::RecordData {
935                            fields,
936                            ..Default::default()
937                        }),
938                        pos,
939                    ))
940                }
941                FieldPathElem::Expr(expr) => {
942                    let pos = expr.pos;
943                    let expr = term::RichTerm::from_ast(expr);
944                    let static_access = expr.as_ref().try_str_chunk_as_static_str();
945
946                    if let Some(static_access) = static_access {
947                        let id = LocIdent::new_with_pos(static_access, pos);
948                        let mut fields = IndexMap::new();
949                        fields.insert(id, acc);
950
951                        term::record::Field::from(term::RichTerm::new(
952                            term::Term::Record(term::record::RecordData {
953                                fields,
954                                ..Default::default()
955                            }),
956                            pos,
957                        ))
958                    } else {
959                        // The record we create isn't recursive, because it is only comprised of
960                        // one dynamic field. It's just simpler to use the infrastructure of
961                        // `RecRecord` to handle dynamic fields at evaluation time rather than
962                        // right here
963                        term::record::Field::from(term::RichTerm::new(
964                            term::Term::RecRecord(
965                                term::record::RecordData::empty(),
966                                Vec::new(),
967                                vec![(expr, acc)],
968                                None,
969                            ),
970                            pos,
971                        ))
972                    }
973                }
974            }
975        });
976
977        (fst.to_mainline(), content)
978    }
979}
980
981impl<'ast> FromAst<record::FieldMetadata<'ast>> for term::record::FieldMetadata {
982    fn from_ast(metadata: &record::FieldMetadata<'ast>) -> Self {
983        let doc = metadata.doc.as_ref().map(|doc| String::from(&**doc));
984
985        term::record::FieldMetadata {
986            doc,
987            annotation: metadata.annotation.to_mainline(),
988            opt: metadata.opt,
989            not_exported: metadata.not_exported,
990            priority: metadata.priority.clone(),
991        }
992    }
993}
994
995impl<'ast> FromAst<record::Include<'ast>> for term::record::Include {
996    fn from_ast(incl: &record::Include<'ast>) -> Self {
997        term::record::Include {
998            ident: incl.ident,
999            metadata: incl.metadata.to_mainline(),
1000        }
1001    }
1002}
1003
1004impl<'ast> FromAst<Type<'ast>> for mline_type::Type {
1005    fn from_ast(typ: &Type<'ast>) -> Self {
1006        mline_type::Type {
1007            typ: typ.typ.to_mainline(),
1008            pos: typ.pos,
1009        }
1010    }
1011}
1012
1013impl<'ast> FromAst<TypeUnr<'ast>> for MainlineTypeUnr {
1014    fn from_ast(typ: &TypeUnr<'ast>) -> Self {
1015        typ.clone().map(
1016            |typ| Box::new(typ.to_mainline()),
1017            |rrows| rrows.to_mainline(),
1018            |erows| erows.to_mainline(),
1019            |ctr| ctr.to_mainline(),
1020        )
1021    }
1022}
1023
1024impl<'ast> FromAst<RecordRows<'ast>> for mline_type::RecordRows {
1025    fn from_ast(rrows: &RecordRows<'ast>) -> Self {
1026        mline_type::RecordRows(rrows.0.to_mainline())
1027    }
1028}
1029
1030impl<'ast> FromAst<EnumRows<'ast>> for mline_type::EnumRows {
1031    fn from_ast(erows: &EnumRows<'ast>) -> Self {
1032        mline_type::EnumRows(erows.0.to_mainline())
1033    }
1034}
1035
1036impl<'ast> FromAst<EnumRowsUnr<'ast>> for MainlineEnumRowsUnr {
1037    fn from_ast(erows: &EnumRowsUnr<'ast>) -> Self {
1038        erows.clone().map(
1039            |typ| Box::new(typ.to_mainline()),
1040            |erows| Box::new(erows.to_mainline()),
1041        )
1042    }
1043}
1044
1045impl<'ast> FromAst<EnumRow<'ast>> for mline_type::EnumRow {
1046    fn from_ast(erow: &EnumRow<'ast>) -> Self {
1047        mline_type::EnumRow {
1048            id: erow.id,
1049            typ: erow.typ.as_ref().map(|ty| Box::new((*ty).to_mainline())),
1050        }
1051    }
1052}
1053
1054impl<'ast> FromAst<RecordRowsUnr<'ast>> for MainlineRecordRowsUnr {
1055    fn from_ast(rrows: &RecordRowsUnr<'ast>) -> Self {
1056        rrows.clone().map(
1057            |typ| Box::new(typ.to_mainline()),
1058            |rrows| Box::new(rrows.to_mainline()),
1059        )
1060    }
1061}
1062
1063impl<'ast> FromAst<RecordRow<'ast>> for mline_type::RecordRow {
1064    fn from_ast(rrow: &RecordRow<'ast>) -> Self {
1065        mline_type::RecordRowF {
1066            id: rrow.id,
1067            typ: Box::new(rrow.typ.to_mainline()),
1068        }
1069    }
1070}
1071
1072impl<'ast> FromAst<Type<'ast>> for term::LabeledType {
1073    fn from_ast(typ: &Type<'ast>) -> Self {
1074        let typ: mline_type::Type = typ.to_mainline();
1075        //TODO:remove
1076        if typ.pos.into_opt().is_none() {
1077            panic!("Expected a position to be set for the type {typ:?}");
1078        }
1079        // We expect the new AST node to always have a position set. In fact we should
1080        // probably switch to `RawSpan` instead of `TermPos` everywhere; but let's do that
1081        // later
1082        let span = typ.pos.unwrap();
1083
1084        term::LabeledType {
1085            typ: typ.clone(),
1086            label: label::Label {
1087                typ: std::rc::Rc::new(typ),
1088                span: Some(span),
1089                ..Default::default()
1090            },
1091        }
1092    }
1093}
1094
1095impl<'ast> FromAst<MatchBranch<'ast>> for term::MatchBranch {
1096    fn from_ast(branch: &MatchBranch<'ast>) -> Self {
1097        term::MatchBranch {
1098            pattern: branch.pattern.to_mainline(),
1099            guard: branch.guard.as_ref().map(|ast| ast.to_mainline()),
1100            body: branch.body.to_mainline(),
1101        }
1102    }
1103}
1104
1105/// One data type representing all possible primops from the mainline AST, whether unary, binary or
1106/// multi-ary.
1107enum TermPrimOp {
1108    Unary(term::UnaryOp),
1109    Binary(term::BinaryOp),
1110    NAry(term::NAryOp),
1111}
1112
1113impl FromAst<PrimOp> for TermPrimOp {
1114    fn from_ast(op: &PrimOp) -> Self {
1115        match op {
1116            PrimOp::Typeof => TermPrimOp::Unary(term::UnaryOp::Typeof),
1117            PrimOp::Cast => TermPrimOp::Unary(term::UnaryOp::Cast),
1118            PrimOp::BoolAnd => TermPrimOp::Unary(term::UnaryOp::BoolAnd),
1119            PrimOp::BoolOr => TermPrimOp::Unary(term::UnaryOp::BoolOr),
1120            PrimOp::BoolNot => TermPrimOp::Unary(term::UnaryOp::BoolNot),
1121            PrimOp::Blame => TermPrimOp::Unary(term::UnaryOp::Blame),
1122            PrimOp::EnumEmbed(loc_ident) => TermPrimOp::Unary(term::UnaryOp::EnumEmbed(*loc_ident)),
1123            PrimOp::RecordStatAccess(loc_ident) => {
1124                TermPrimOp::Unary(term::UnaryOp::RecordAccess(*loc_ident))
1125            }
1126            PrimOp::ArrayMap => TermPrimOp::Unary(term::UnaryOp::ArrayMap),
1127            PrimOp::RecordMap => TermPrimOp::Unary(term::UnaryOp::RecordMap),
1128            PrimOp::LabelFlipPol => TermPrimOp::Unary(term::UnaryOp::LabelFlipPol),
1129            PrimOp::LabelPol => TermPrimOp::Unary(term::UnaryOp::LabelPol),
1130            PrimOp::LabelGoDom => TermPrimOp::Unary(term::UnaryOp::LabelGoDom),
1131            PrimOp::LabelGoCodom => TermPrimOp::Unary(term::UnaryOp::LabelGoCodom),
1132            PrimOp::LabelGoArray => TermPrimOp::Unary(term::UnaryOp::LabelGoArray),
1133            PrimOp::LabelGoDict => TermPrimOp::Unary(term::UnaryOp::LabelGoDict),
1134            PrimOp::Seq => TermPrimOp::Unary(term::UnaryOp::Seq),
1135            PrimOp::DeepSeq => TermPrimOp::Unary(term::UnaryOp::DeepSeq),
1136            PrimOp::ArrayLength => TermPrimOp::Unary(term::UnaryOp::ArrayLength),
1137            PrimOp::ArrayGen => TermPrimOp::Unary(term::UnaryOp::ArrayGen),
1138            PrimOp::RecordFields(record_op_kind) => {
1139                TermPrimOp::Unary(term::UnaryOp::RecordFields(*record_op_kind))
1140            }
1141            PrimOp::RecordValues => TermPrimOp::Unary(term::UnaryOp::RecordValues),
1142            PrimOp::StringTrim => TermPrimOp::Unary(term::UnaryOp::StringTrim),
1143            PrimOp::StringChars => TermPrimOp::Unary(term::UnaryOp::StringChars),
1144            PrimOp::StringUppercase => TermPrimOp::Unary(term::UnaryOp::StringUppercase),
1145            PrimOp::StringLowercase => TermPrimOp::Unary(term::UnaryOp::StringLowercase),
1146            PrimOp::StringLength => TermPrimOp::Unary(term::UnaryOp::StringLength),
1147            PrimOp::ToString => TermPrimOp::Unary(term::UnaryOp::ToString),
1148            PrimOp::NumberFromString => TermPrimOp::Unary(term::UnaryOp::NumberFromString),
1149            PrimOp::EnumFromString => TermPrimOp::Unary(term::UnaryOp::EnumFromString),
1150            PrimOp::StringIsMatch => TermPrimOp::Unary(term::UnaryOp::StringIsMatch),
1151            PrimOp::StringFind => TermPrimOp::Unary(term::UnaryOp::StringFind),
1152            PrimOp::StringFindAll => TermPrimOp::Unary(term::UnaryOp::StringFindAll),
1153            PrimOp::Force {
1154                ignore_not_exported,
1155            } => TermPrimOp::Unary(term::UnaryOp::Force {
1156                ignore_not_exported: *ignore_not_exported,
1157            }),
1158            PrimOp::RecordEmptyWithTail => TermPrimOp::Unary(term::UnaryOp::RecordEmptyWithTail),
1159            PrimOp::RecordFreeze => TermPrimOp::Unary(term::UnaryOp::RecordFreeze),
1160            PrimOp::Trace => TermPrimOp::Unary(term::UnaryOp::Trace),
1161            PrimOp::LabelPushDiag => TermPrimOp::Unary(term::UnaryOp::LabelPushDiag),
1162            PrimOp::EnumGetArg => TermPrimOp::Unary(term::UnaryOp::EnumGetArg),
1163            PrimOp::EnumMakeVariant => TermPrimOp::Unary(term::UnaryOp::EnumMakeVariant),
1164            PrimOp::EnumIsVariant => TermPrimOp::Unary(term::UnaryOp::EnumIsVariant),
1165            PrimOp::EnumGetTag => TermPrimOp::Unary(term::UnaryOp::EnumGetTag),
1166            PrimOp::ContractCustom => TermPrimOp::Unary(term::UnaryOp::ContractCustom),
1167            PrimOp::NumberArcCos => TermPrimOp::Unary(term::UnaryOp::NumberArcCos),
1168            PrimOp::NumberArcSin => TermPrimOp::Unary(term::UnaryOp::NumberArcSin),
1169            PrimOp::NumberArcTan => TermPrimOp::Unary(term::UnaryOp::NumberArcTan),
1170            PrimOp::NumberCos => TermPrimOp::Unary(term::UnaryOp::NumberCos),
1171            PrimOp::NumberSin => TermPrimOp::Unary(term::UnaryOp::NumberSin),
1172            PrimOp::NumberTan => TermPrimOp::Unary(term::UnaryOp::NumberTan),
1173            #[cfg(feature = "nix-experimental")]
1174            PrimOp::EvalNix => TermPrimOp::Unary(term::UnaryOp::EvalNix),
1175
1176            // Binary operations
1177            PrimOp::Plus => TermPrimOp::Binary(term::BinaryOp::Plus),
1178            PrimOp::Sub => TermPrimOp::Binary(term::BinaryOp::Sub),
1179            PrimOp::Mult => TermPrimOp::Binary(term::BinaryOp::Mult),
1180            PrimOp::Div => TermPrimOp::Binary(term::BinaryOp::Div),
1181            PrimOp::Modulo => TermPrimOp::Binary(term::BinaryOp::Modulo),
1182            PrimOp::NumberArcTan2 => TermPrimOp::Binary(term::BinaryOp::NumberArcTan2),
1183            PrimOp::NumberLog => TermPrimOp::Binary(term::BinaryOp::NumberLog),
1184            PrimOp::Pow => TermPrimOp::Binary(term::BinaryOp::Pow),
1185            PrimOp::StringConcat => TermPrimOp::Binary(term::BinaryOp::StringConcat),
1186            PrimOp::Eq => TermPrimOp::Binary(term::BinaryOp::Eq),
1187            PrimOp::LessThan => TermPrimOp::Binary(term::BinaryOp::LessThan),
1188            PrimOp::LessOrEq => TermPrimOp::Binary(term::BinaryOp::LessOrEq),
1189            PrimOp::GreaterThan => TermPrimOp::Binary(term::BinaryOp::GreaterThan),
1190            PrimOp::GreaterOrEq => TermPrimOp::Binary(term::BinaryOp::GreaterOrEq),
1191            PrimOp::ContractApply => TermPrimOp::Binary(term::BinaryOp::ContractApply),
1192            PrimOp::ContractCheck => TermPrimOp::Binary(term::BinaryOp::ContractCheck),
1193            PrimOp::LabelWithErrorData => TermPrimOp::Binary(term::BinaryOp::LabelWithErrorData),
1194            PrimOp::LabelGoField => TermPrimOp::Binary(term::BinaryOp::LabelGoField),
1195            PrimOp::RecordInsert(record_op_kind) => {
1196                TermPrimOp::Binary(term::BinaryOp::RecordInsert {
1197                    metadata: Default::default(),
1198                    pending_contracts: Vec::new(),
1199                    ext_kind: term::RecordExtKind::WithValue,
1200                    op_kind: *record_op_kind,
1201                })
1202            }
1203            PrimOp::RecordRemove(record_op_kind) => {
1204                TermPrimOp::Binary(term::BinaryOp::RecordRemove(*record_op_kind))
1205            }
1206            PrimOp::RecordGet => TermPrimOp::Binary(term::BinaryOp::RecordGet),
1207            PrimOp::RecordHasField(record_op_kind) => {
1208                TermPrimOp::Binary(term::BinaryOp::RecordHasField(*record_op_kind))
1209            }
1210            PrimOp::RecordFieldIsDefined(record_op_kind) => {
1211                TermPrimOp::Binary(term::BinaryOp::RecordFieldIsDefined(*record_op_kind))
1212            }
1213            PrimOp::RecordSplitPair => TermPrimOp::Binary(term::BinaryOp::RecordSplitPair),
1214            PrimOp::RecordDisjointMerge => TermPrimOp::Binary(term::BinaryOp::RecordDisjointMerge),
1215            PrimOp::ArrayConcat => TermPrimOp::Binary(term::BinaryOp::ArrayConcat),
1216            PrimOp::ArrayAt => TermPrimOp::Binary(term::BinaryOp::ArrayAt),
1217            PrimOp::Merge(merge_kind) => {
1218                // [^merge-label-span] The mainline AST requires a `MergeLabel` object, itself
1219                // demanding a `RawSpan` that we can't provide here - it's stored higher up in the
1220                // AST, at the `PrimOpApp` node. We generate a dummy span and rely on the caller
1221                // (in practice `FromAst<Node<'_>>`) to post-process a merge primop application,
1222                // setting the span of the dummy merge label correctly.
1223                let dummy_label: label::MergeLabel = label::Label::dummy().into();
1224
1225                TermPrimOp::Binary(term::BinaryOp::Merge(label::MergeLabel {
1226                    kind: *merge_kind,
1227                    ..dummy_label
1228                }))
1229            }
1230            PrimOp::Hash => TermPrimOp::Binary(term::BinaryOp::Hash),
1231            PrimOp::Serialize => TermPrimOp::Binary(term::BinaryOp::Serialize),
1232            PrimOp::Deserialize => TermPrimOp::Binary(term::BinaryOp::Deserialize),
1233            PrimOp::StringSplit => TermPrimOp::Binary(term::BinaryOp::StringSplit),
1234            PrimOp::StringContains => TermPrimOp::Binary(term::BinaryOp::StringContains),
1235            PrimOp::StringCompare => TermPrimOp::Binary(term::BinaryOp::StringCompare),
1236            PrimOp::Seal => TermPrimOp::Binary(term::BinaryOp::Seal),
1237            PrimOp::Unseal => TermPrimOp::Binary(term::BinaryOp::Unseal),
1238            PrimOp::ContractArrayLazyApp => {
1239                TermPrimOp::Binary(term::BinaryOp::ContractArrayLazyApp)
1240            }
1241            PrimOp::ContractRecordLazyApp => {
1242                TermPrimOp::Binary(term::BinaryOp::ContractRecordLazyApp)
1243            }
1244            PrimOp::LabelWithMessage => TermPrimOp::Binary(term::BinaryOp::LabelWithMessage),
1245            PrimOp::LabelWithNotes => TermPrimOp::Binary(term::BinaryOp::LabelWithNotes),
1246            PrimOp::LabelAppendNote => TermPrimOp::Binary(term::BinaryOp::LabelAppendNote),
1247            PrimOp::LabelLookupTypeVar => TermPrimOp::Binary(term::BinaryOp::LabelLookupTypeVar),
1248
1249            // N-ary operations
1250            PrimOp::StringReplace => TermPrimOp::NAry(term::NAryOp::StringReplace),
1251            PrimOp::StringReplaceRegex => TermPrimOp::NAry(term::NAryOp::StringReplaceRegex),
1252            PrimOp::StringSubstr => TermPrimOp::NAry(term::NAryOp::StringSubstr),
1253            PrimOp::MergeContract => TermPrimOp::NAry(term::NAryOp::MergeContract),
1254            PrimOp::RecordSealTail => TermPrimOp::NAry(term::NAryOp::RecordSealTail),
1255            PrimOp::RecordUnsealTail => TermPrimOp::NAry(term::NAryOp::RecordUnsealTail),
1256            PrimOp::LabelInsertTypeVar => TermPrimOp::NAry(term::NAryOp::LabelInsertTypeVar),
1257            PrimOp::ArraySlice => TermPrimOp::NAry(term::NAryOp::ArraySlice),
1258        }
1259    }
1260}
1261
1262impl<'ast> FromAst<Node<'ast>> for term::Term {
1263    fn from_ast(node: &Node<'ast>) -> Self {
1264        use term::Term;
1265
1266        match node {
1267            Node::Null => Term::Null,
1268            Node::Bool(b) => Term::Bool(*b),
1269            Node::Number(n) => Term::Num((**n).clone()),
1270            Node::String(s) => Term::Str((*s).into()),
1271            Node::StringChunks(chunks) => {
1272                let chunks = chunks
1273                    .iter()
1274                    .rev()
1275                    .map(|chunk| match chunk {
1276                        StringChunk::Literal(s) => term::StrChunk::Literal(s.clone()),
1277                        StringChunk::Expr(expr, indent) => {
1278                            term::StrChunk::Expr(expr.to_mainline(), *indent)
1279                        }
1280                    })
1281                    .collect();
1282
1283                Term::StrChunks(chunks)
1284            }
1285            Node::Fun { args, body } => {
1286                let body_pos = body.pos;
1287
1288                // We transform a n-ary function representation to a chain of nested unary
1289                // functions, the latter being the representation used in the mainline AST.
1290                args.iter()
1291                    .rev()
1292                    .fold(term::RichTerm::from_ast(body), |acc, arg| {
1293                        let term = match arg.data {
1294                            PatternData::Any(id) => Term::Fun(id, acc),
1295                            _ => term::Term::FunPattern((*arg).to_mainline(), acc),
1296                        };
1297
1298                        // [^nary-constructors-unrolling]: this case is a bit annoying: we need to
1299                        // extract the position of the intermediate created functions to satisfy
1300                        // the old AST structure, but this information isn't available directly.
1301                        //
1302                        // What we do here is to fuse the span of the term being built and the one
1303                        // of the current argument, which should be a reasonable approximation (if
1304                        // not exactly the same thing).
1305                        term::RichTerm::new(term, arg.pos.fuse(body_pos))
1306                    })
1307                    .term
1308                    .into_owned()
1309            }
1310            Node::Let {
1311                bindings,
1312                body,
1313                rec,
1314            } => {
1315                // Mainline term bindings can't have any metadata associated with them. We need to
1316                // rewrite let metadata to be free-standing type and contract annotations instead,
1317                // which is achieved by this helper.
1318                fn with_metadata(metadata: &LetMetadata<'_>, value: &Ast<'_>) -> term::RichTerm {
1319                    let value: term::RichTerm = value.to_mainline();
1320                    let pos = value.pos;
1321
1322                    if metadata.annotation.is_empty() {
1323                        return value;
1324                    }
1325
1326                    term::RichTerm::new(
1327                        term::Term::Annotated(metadata.annotation.to_mainline(), value),
1328                        pos,
1329                    )
1330                }
1331
1332                // We try to collect all patterns as single identifiers. If this works, we can emit
1333                // a simpler / more compact `Let`.
1334                let try_bindings = bindings
1335                    .iter()
1336                    .map(
1337                        |LetBinding {
1338                             pattern,
1339                             metadata,
1340                             value,
1341                         }| match pattern.data {
1342                            PatternData::Any(id) => Some((id, with_metadata(metadata, value))),
1343                            _ => None,
1344                        },
1345                    )
1346                    .collect::<Option<SmallVec<_>>>();
1347
1348                let body = body.to_mainline();
1349                let attrs = term::LetAttrs {
1350                    rec: *rec,
1351                    ..Default::default()
1352                };
1353
1354                if let Some(bindings) = try_bindings {
1355                    Term::Let(bindings, body, attrs)
1356                } else {
1357                    let bindings = bindings
1358                        .iter()
1359                        .map(
1360                            |LetBinding {
1361                                 pattern,
1362                                 value,
1363                                 metadata,
1364                             }| {
1365                                (pattern.to_mainline(), with_metadata(metadata, value))
1366                            },
1367                        )
1368                        .collect();
1369
1370                    Term::LetPattern(bindings, body, attrs)
1371                }
1372            }
1373            Node::App { head, args } => {
1374                let head_pos = head.pos;
1375
1376                // We transform a n-ary application representation to a chain of nested unary
1377                // applications, the latter being the representation used in the mainline AST.
1378                args.iter()
1379                    .fold(head.to_mainline(), |result, arg| {
1380                        // see [^nary-constructors-unrolling]
1381                        let arg_pos = arg.pos;
1382                        term::RichTerm::new(
1383                            Term::App(result, arg.to_mainline()),
1384                            head_pos.fuse(arg_pos),
1385                        )
1386                    })
1387                    .term
1388                    .into_owned()
1389            }
1390            Node::Var(loc_ident) => Term::Var(*loc_ident),
1391            Node::EnumVariant { tag, arg } => {
1392                if let Some(arg) = arg {
1393                    Term::EnumVariant {
1394                        tag: *tag,
1395                        arg: arg.to_mainline(),
1396                        attrs: term::EnumVariantAttrs::default(),
1397                    }
1398                } else {
1399                    Term::Enum(*tag)
1400                }
1401            }
1402            Node::Record(record) => {
1403                let (data, dyn_fields) = (*record).to_mainline();
1404                Term::RecRecord(
1405                    data,
1406                    record
1407                        .includes
1408                        .iter()
1409                        .map(|incl| incl.to_mainline())
1410                        .collect(),
1411                    dyn_fields,
1412                    None,
1413                )
1414            }
1415            Node::IfThenElse {
1416                cond,
1417                then_branch,
1418                else_branch,
1419            } => term::make::if_then_else(
1420                term::RichTerm::from_ast(cond),
1421                term::RichTerm::from_ast(then_branch),
1422                term::RichTerm::from_ast(else_branch),
1423            )
1424            .term
1425            .into_owned(),
1426            Node::Match(data) => {
1427                let branches = data.branches.iter().map(ToMainline::to_mainline).collect();
1428
1429                Term::Match(term::MatchData { branches })
1430            }
1431            Node::Array(array) => {
1432                let array = array.iter().map(ToMainline::to_mainline).collect();
1433                Term::Array(array, term::array::ArrayAttrs::default())
1434            }
1435            Node::PrimOpApp { op, args } => match (*op).to_mainline() {
1436                TermPrimOp::Unary(op) => Term::Op1(op, args[0].to_mainline()),
1437                // If `op` is `Merge`, we need to patch the span of the merge label with the
1438                // correct value. Unfortunately, we still don't have access to the right span,
1439                // which is the position of this whole node. We delegate this to the caller, that
1440                // is `from_ast::<Ast<'ast>>`. See [^merge-label-span].
1441                TermPrimOp::Binary(op) => {
1442                    Term::Op2(op, args[0].to_mainline(), args[1].to_mainline())
1443                }
1444                TermPrimOp::NAry(op) => {
1445                    Term::OpN(op, args.iter().map(|arg| (*arg).to_mainline()).collect())
1446                }
1447            },
1448            Node::Annotated { annot, inner } => {
1449                Term::Annotated((*annot).to_mainline(), inner.to_mainline())
1450            }
1451            Node::Import(Import::Path { path, format }) => Term::Import(term::Import::Path {
1452                path: (*path).to_owned(),
1453                format: *format,
1454            }),
1455            Node::Import(Import::Package { id }) => Term::Import(term::Import::Package { id: *id }),
1456            Node::Type(typ) => {
1457                let typ: mline_type::Type = (*typ).to_mainline();
1458
1459                let contract = typ
1460                    .contract()
1461                    // It would be painful to change the interface of `ToMainline` and make it
1462                    // fallible just for this one special case. Instead, if the contract
1463                    // conversion causes an unbound variable error (which it shouldn't anyway if
1464                    // the term has been correctly typechecked), we pack this error as parse
1465                    // error in the AST.
1466                    .unwrap_or_else(|err| {
1467                        Term::ParseError(ParseError::UnboundTypeVariables(vec![err.0])).into()
1468                    });
1469
1470                Term::Type { typ, contract }
1471            }
1472            Node::ParseError(error) => Term::ParseError((*error).clone()),
1473        }
1474    }
1475}
1476
1477impl<'ast> FromAst<Ast<'ast>> for term::RichTerm {
1478    fn from_ast(ast: &Ast<'ast>) -> Self {
1479        let mut result = term::RichTerm::new(ast.node.to_mainline(), ast.pos);
1480        // See [^merge-label-span]
1481        if let term::Term::Op2(term::BinaryOp::Merge(ref mut label), _, _) =
1482            term::SharedTerm::make_mut(&mut result.term)
1483        {
1484            // unwrap(): we expect all position to be set in the new AST (should be using span
1485            // directly in the future)
1486            label.span = Some(ast.pos.unwrap());
1487        }
1488
1489        result
1490    }
1491}
1492
1493impl<'ast> FromAst<&'ast Ast<'ast>> for term::RichTerm {
1494    fn from_ast(ast: &&'ast Ast<'ast>) -> Self {
1495        FromAst::from_ast(*ast)
1496    }
1497}
1498
1499/// Convert a record definition to a mainline recursive record definition (with static and dynamic
1500/// fields). If a field is defined several times, the different definitions are merged, statically
1501/// if possible.
1502impl<'ast> FromAst<Record<'ast>>
1503    for (
1504        term::record::RecordData,
1505        Vec<(term::RichTerm, term::record::Field)>,
1506    )
1507{
1508    fn from_ast(record: &Record<'ast>) -> Self {
1509        use indexmap::map::Entry;
1510
1511        fn insert_static_field(
1512            static_fields: &mut IndexMap<LocIdent, term::record::Field>,
1513            id: LocIdent,
1514            field: term::record::Field,
1515        ) {
1516            match static_fields.entry(id) {
1517                Entry::Occupied(mut occpd) => {
1518                    // temporarily putting an empty field in the entry to take the previous value.
1519                    let prev = occpd.insert(term::record::Field::default());
1520
1521                    // unwrap(): the field's identifier must have a position during parsing.
1522                    occpd.insert(merge_fields(id.pos.unwrap(), prev, field));
1523                }
1524                Entry::Vacant(vac) => {
1525                    vac.insert(field);
1526                }
1527            }
1528        }
1529
1530        let mut static_fields = IndexMap::new();
1531        let mut dynamic_fields = Vec::new();
1532
1533        for def in record.field_defs.iter().map(ToMainline::to_mainline) {
1534            match def {
1535                (FieldName::Ident(id), field) => insert_static_field(&mut static_fields, id, field),
1536                (FieldName::Expr(expr), field) => {
1537                    let pos = expr.pos;
1538                    // Dynamic fields (whose name is defined by an interpolated string) have a different
1539                    // semantics than fields whose name can be determined statically. However, static
1540                    // fields with special characters are also parsed as string chunks:
1541                    //
1542                    // ```
1543                    // let x = "dynamic" in {"I%am.static" = false, "%{x}" = true}
1544                    // ```
1545                    //
1546                    // Here, both fields are parsed as `StrChunks`, but the first field is actually a
1547                    // static one, just with special characters. The following code determines which fields
1548                    // are actually static or not, and inserts them in the right location.
1549                    let static_access = expr.term.as_ref().try_str_chunk_as_static_str();
1550
1551                    if let Some(static_access) = static_access {
1552                        insert_static_field(
1553                            &mut static_fields,
1554                            LocIdent::new_with_pos(static_access, pos),
1555                            field,
1556                        )
1557                    } else {
1558                        dynamic_fields.push((expr, field));
1559                    }
1560                }
1561            }
1562        }
1563
1564        (
1565            term::record::RecordData::new(
1566                static_fields,
1567                term::record::RecordAttrs {
1568                    open: record.open,
1569                    ..Default::default()
1570                },
1571                None,
1572            ),
1573            dynamic_fields,
1574        )
1575    }
1576}
1577
1578/// Merge two fields by performing the merge of both their value (dynamically if
1579/// necessary, by introducing a merge operator) and their metadata (statically).
1580///
1581/// If the values of both fields are static records ([`term::Term::Record`]s), their merge is
1582/// computed statically. This prevents building terms whose depth is linear in the number of fields
1583/// if partial definitions are involved. This manifested in
1584/// https://github.com/tweag/nickel/issues/1427.
1585///
1586/// This is a helper for the conversion of a record definition to mainline.
1587fn merge_fields(
1588    id_span: RawSpan,
1589    field1: term::record::Field,
1590    field2: term::record::Field,
1591) -> term::record::Field {
1592    use crate::eval::merge::{merge_doc, split};
1593    use term::{make as mk_term, record::RecordData, BinaryOp, RichTerm, Term};
1594
1595    // FIXME: We're duplicating a lot of the logic in
1596    // [`eval::merge::merge_fields`] but not quite enough to actually factor
1597    // it out
1598    fn merge_values(id_span: RawSpan, t1: term::RichTerm, t2: term::RichTerm) -> term::RichTerm {
1599        let RichTerm {
1600            term: t1,
1601            pos: pos1,
1602        } = t1;
1603        let RichTerm {
1604            term: t2,
1605            pos: pos2,
1606        } = t2;
1607        match (t1.into_owned(), t2.into_owned()) {
1608            (Term::Record(rd1), Term::Record(rd2)) => {
1609                let split::SplitResult {
1610                    left,
1611                    center,
1612                    right,
1613                } = split::split(rd1.fields, rd2.fields);
1614                let mut fields = IndexMap::with_capacity(left.len() + center.len() + right.len());
1615                fields.extend(left);
1616                fields.extend(right);
1617                for (id, (field1, field2)) in center.into_iter() {
1618                    fields.insert(id, merge_fields(id_span, field1, field2));
1619                }
1620                Term::Record(RecordData::new(
1621                    fields,
1622                    Combine::combine(rd1.attrs, rd2.attrs),
1623                    None,
1624                ))
1625                .into()
1626            }
1627            (t1, t2) => mk_term::op2(
1628                BinaryOp::Merge(label::MergeLabel {
1629                    span: Some(id_span),
1630                    kind: label::MergeKind::PiecewiseDef,
1631                }),
1632                RichTerm::new(t1, pos1),
1633                RichTerm::new(t2, pos2),
1634            ),
1635        }
1636    }
1637
1638    let (value, priority) = match (field1.value, field2.value) {
1639        (Some(t1), Some(t2)) if field1.metadata.priority == field2.metadata.priority => (
1640            Some(merge_values(id_span, t1, t2)),
1641            field1.metadata.priority,
1642        ),
1643        (Some(t), _) if field1.metadata.priority > field2.metadata.priority => {
1644            (Some(t), field1.metadata.priority)
1645        }
1646        (_, Some(t)) if field1.metadata.priority < field2.metadata.priority => {
1647            (Some(t), field2.metadata.priority)
1648        }
1649        (Some(t), None) => (Some(t), field1.metadata.priority),
1650        (None, Some(t)) => (Some(t), field2.metadata.priority),
1651        (None, None) => (None, Default::default()),
1652        _ => unreachable!(),
1653    };
1654
1655    // At this stage, pending contracts aren't filled nor meaningful, and should all be empty.
1656    debug_assert!(field1.pending_contracts.is_empty() && field2.pending_contracts.is_empty());
1657
1658    term::record::Field {
1659        value,
1660        // [`FieldMetadata::combine`] produces subtly different behaviour from the runtime merging
1661        // code: we don't use [`Combine::combine`] here and replicate the merging logic instead.
1662        metadata: term::record::FieldMetadata {
1663            doc: merge_doc(field1.metadata.doc, field2.metadata.doc),
1664            annotation: Combine::combine(field1.metadata.annotation, field2.metadata.annotation),
1665            opt: field1.metadata.opt && field2.metadata.opt,
1666            not_exported: field1.metadata.not_exported || field2.metadata.not_exported,
1667            priority,
1668        },
1669        pending_contracts: Vec::new(),
1670    }
1671}