Skip to main content

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