Skip to main content

nickel_lang_core/term/
record.rs

1use super::*;
2use crate::{
3    combine::Combine,
4    error::EvalErrorKind,
5    identifier::{Ident, LocIdent},
6    label::Label,
7    position::PosIdx,
8};
9use std::{collections::HashSet, rc::Rc};
10
11/// Additional attributes for record.
12#[derive(Debug, Default, Eq, PartialEq, Copy, Clone)]
13pub struct RecordAttrs {
14    /// If the record is an open record, ie ending with `..`. Open records have a different
15    /// behavior when used as a record contract: they allow additional fields to be present.
16    pub open: bool,
17    /// If the record has been frozen.
18    ///
19    /// A recursive record is frozen when all the lazy contracts are applied to their corresponding
20    /// fields and flushed from the lazy contracts list. The values of the fields are computed but
21    /// all dependencies are erased. That is, we turn a recursive, overridable record into a static
22    /// dictionary. The information about field dependencies is lost and future overriding won't
23    /// update reverse dependencies.
24    ///
25    /// We store this information for performance reason: freezing is expensive (linear in the
26    /// number of fields of the record), and we might need to do it on every dictionary operation
27    /// such as `insert`, `remove`, etc. (see
28    /// [#1877](https://github.com/tweag/nickel/issues/1877)). This flags avoid repeated, useless
29    /// freezing.
30    pub frozen: bool,
31}
32
33impl RecordAttrs {
34    pub fn new() -> Self {
35        Self::default()
36    }
37
38    /// Sets the `frozen` flag to true and return the updated attributes.
39    pub fn frozen(mut self) -> Self {
40        self.frozen = true;
41        self
42    }
43}
44
45impl Combine for RecordAttrs {
46    fn combine(left: Self, right: Self) -> Self {
47        RecordAttrs {
48            open: left.open || right.open,
49            frozen: left.frozen && right.frozen,
50        }
51    }
52}
53
54/// Dependencies of a field or a cache element over the other recursive fields of a recursive
55/// record.
56#[derive(Clone, Debug, PartialEq, Eq)]
57pub enum FieldDeps {
58    /// The set of dependencies is fixed and has been computed. When attached to an element, an
59    /// empty set of dependency means that the element isn't revertible, but standard.
60    Known(Rc<HashSet<Ident>>),
61
62    /// The element is revertible, but the set of dependencies hasn't been computed. In that case,
63    /// the interpreter should be conservative and assume that any recursive references can appear
64    /// in the content of the corresponding element.
65    Unknown,
66}
67
68impl FieldDeps {
69    /// Compute the union of two cache elements dependencies. [`FieldDeps::Unknown`] can be see as
70    /// the top element, meaning that if one of the two set of dependencies is
71    /// [`FieldDeps::Unknown`], so is the result.
72    pub fn union(self, other: Self) -> Self {
73        match (self, other) {
74            // If one of the field has unknown dependencies (understand: may depend on all the other
75            // fields), then the resulting fields has unknown dependencies as well
76            (FieldDeps::Unknown, _) | (_, FieldDeps::Unknown) => FieldDeps::Unknown,
77            (FieldDeps::Known(deps1), FieldDeps::Known(deps2)) => {
78                let union: HashSet<Ident> = deps1.union(&*deps2).cloned().collect();
79                FieldDeps::Known(Rc::new(union))
80            }
81        }
82    }
83
84    /// Return an empty set of dependencies
85    pub fn empty() -> Self {
86        FieldDeps::Known(Rc::new(HashSet::new()))
87    }
88
89    /// Return `true` if the dependencies are known and are empty, or `false` otherwise.
90    pub fn is_empty(&self) -> bool {
91        matches!(self, FieldDeps::Known(deps) if deps.is_empty())
92    }
93}
94
95impl From<HashSet<Ident>> for FieldDeps {
96    fn from(set: HashSet<Ident>) -> Self {
97        FieldDeps::Known(Rc::new(set))
98    }
99}
100
101/// Store field interdependencies in a recursive record. Map each static, dynamic and included
102/// field to the set of recursive fields that syntactically appear in their definition as free
103/// variables.
104#[derive(Debug, Default, Eq, PartialEq, Clone)]
105pub struct RecordDeps {
106    /// Must have exactly the same keys as the static fields map of the recursive record and the
107    /// include expressions. Static fields and include expressions are combined because at the time
108    /// the evaluator uses the dependencies, include expressions don't exist anymore: they have
109    /// already been elaborated to static fields and inserted.
110    pub stat_fields: IndexMap<Ident, FieldDeps>,
111    /// Must have exactly the same length as the dynamic fields list of the recursive record.
112    pub dyn_fields: Vec<FieldDeps>,
113}
114
115#[derive(Clone, Debug, PartialEq)]
116/// An include expression (see [crate::ast::record::Include]).
117pub struct Include {
118    /// The included identifier.
119    pub ident: LocIdent,
120    /// The field metadata.
121    pub metadata: FieldMetadata,
122}
123
124/// The metadata attached to record fields.
125#[derive(Debug, PartialEq, Clone, Default)]
126pub struct FieldMetadata {
127    pub doc: Option<Rc<str>>,
128    pub annotation: TypeAnnotation,
129    /// If the field is optional.
130    pub opt: bool,
131    /// If the field is serialized.
132    pub not_exported: bool,
133    pub priority: MergePriority,
134}
135
136impl FieldMetadata {
137    pub fn new() -> Self {
138        Default::default()
139    }
140
141    /// Checks if those metadata are empty, that is if `self` is the same as [Self::default].
142    pub fn is_empty(&self) -> bool {
143        self.doc.is_none()
144            && self.annotation.is_empty()
145            && !self.opt
146            && !self.not_exported
147            && matches!(self.priority, MergePriority::Neutral)
148    }
149
150    /// Set the `field_name` attribute of the labels of the type and contracts annotations.
151    pub fn with_field_name(mut self, name: Option<LocIdent>) -> Self {
152        self.annotation = self.annotation.with_field_name(name);
153        self
154    }
155}
156
157impl Combine for FieldMetadata {
158    fn combine(left: Self, right: Self) -> Self {
159        let priority = match (left.priority, right.priority) {
160            // Neutral corresponds to the case where no priority was specified. In that case, the
161            // other priority takes precedence.
162            (MergePriority::Neutral, p) | (p, MergePriority::Neutral) => p,
163            // Otherwise, we keep the maximum of both priorities, as we would do when merging
164            // values.
165            (p1, p2) => std::cmp::max(p1, p2),
166        };
167
168        FieldMetadata {
169            doc: crate::eval::merge::merge_doc(left.doc, right.doc),
170            annotation: Combine::combine(left.annotation, right.annotation),
171            opt: left.opt || right.opt,
172            // The resulting field will be suppressed from serialization if either of the fields to be merged is.
173            not_exported: left.not_exported || right.not_exported,
174            priority,
175        }
176    }
177}
178
179impl Combine for SharedMetadata {
180    fn combine(left: Self, right: Self) -> Self {
181        match (left.0, right.0) {
182            (None, None) => SharedMetadata(None),
183            (None, m @ Some(_)) | (m @ Some(_), None) => SharedMetadata(m),
184            (Some(m1), Some(m2)) => {
185                Combine::combine(Rc::unwrap_or_clone(m1), Rc::unwrap_or_clone(m2)).into()
186            }
187        }
188    }
189}
190
191impl From<TypeAnnotation> for FieldMetadata {
192    fn from(annotation: TypeAnnotation) -> Self {
193        FieldMetadata {
194            annotation,
195            ..Default::default()
196        }
197    }
198}
199
200/// A reference-counted wrapper around [FieldMetadata] to allow sharing metadata between multiple
201/// fields. An `Option` layer is added to allow representing the absence of metadata without any
202/// allocation: since each and every [Field] has a `metadata` field, empty metadata would lead to
203/// significant useless allocations if it wasn't optional.
204///
205/// Converting from [`FieldMetadata`] or `Rc<FieldMetadata>` will automatically set `self.0` to
206/// `None` and discard the metadata if they are empty.
207#[derive(Clone, Default, PartialEq, Debug)]
208pub struct SharedMetadata(pub Option<Rc<FieldMetadata>>);
209
210impl From<FieldMetadata> for SharedMetadata {
211    fn from(metadata: FieldMetadata) -> Self {
212        if metadata.is_empty() {
213            SharedMetadata(None)
214        } else {
215            SharedMetadata(Some(Rc::new(metadata)))
216        }
217    }
218}
219
220impl From<Rc<FieldMetadata>> for SharedMetadata {
221    fn from(metadata: Rc<FieldMetadata>) -> Self {
222        if metadata.is_empty() {
223            SharedMetadata(None)
224        } else {
225            SharedMetadata(Some(metadata))
226        }
227    }
228}
229
230impl SharedMetadata {
231    /// Extracts [FieldMetadata] from `self`:
232    ///
233    /// - If `self.0` is `None`, [FieldMetadata::default()] is returned
234    /// - If `self.0` is `Some(rc)`, then [Rc::unwrap_or_clone] is used
235    pub fn into_inner(self) -> FieldMetadata {
236        self.0.map(Rc::unwrap_or_clone).unwrap_or_default()
237    }
238
239    /// Clone the inner [FieldMetadata] value, or returns [FieldMetadata::default] if `self.0` is
240    /// `None`.
241    pub fn clone_inner(&self) -> FieldMetadata {
242        self.0.as_ref().map(|rc| (**rc).clone()).unwrap_or_default()
243    }
244
245    pub fn empty() -> Self {
246        Self::default()
247    }
248
249    pub fn is_empty(&self) -> bool {
250        self.0.as_ref().is_some_and(|m| m.is_empty()) || self.0.is_none()
251    }
252
253    /// Whether this metadata marks the field as not exported. Returns the default value (`false`)
254    /// if there is no metadata.
255    pub fn not_exported(&self) -> bool {
256        self.0.as_ref().is_some_and(|m| m.not_exported)
257    }
258
259    /// Whether this metadata marks the field as optional. Returns the default value (`false`)
260    /// if there is no metadata.
261    pub fn opt(&self) -> bool {
262        self.0.as_ref().is_some_and(|m| m.opt)
263    }
264
265    pub fn priority(&self) -> &MergePriority {
266        self.0
267            .as_ref()
268            .map(|m| &m.priority)
269            .unwrap_or(&MergePriority::Neutral)
270    }
271
272    pub fn doc(&self) -> Option<&str> {
273        self.0.as_ref().and_then(|m| m.doc.as_ref()).map(|s| &**s)
274    }
275
276    pub fn as_ref(&self) -> Option<&FieldMetadata> {
277        self.0.as_deref()
278    }
279
280    pub fn iter_annots(&self) -> impl Iterator<Item = &LabeledType> {
281        self.0.iter().flat_map(|m| m.annotation.iter())
282    }
283}
284
285/// A record field with its metadata.
286#[derive(Clone, Default, PartialEq, Debug)]
287pub struct Field {
288    /// The value is optional because record field may not have a definition (e.g. optional fields).
289    pub value: Option<NickelValue>,
290    pub metadata: SharedMetadata,
291    /// List of contracts yet to be applied.
292    /// These are only observed when data enter or leave the record.
293    pub pending_contracts: Vec<RuntimeContract>,
294}
295
296impl From<NickelValue> for Field {
297    fn from(value: NickelValue) -> Self {
298        Field {
299            value: Some(value),
300            ..Default::default()
301        }
302    }
303}
304
305impl From<TypeAnnotation> for Field {
306    fn from(annotation: TypeAnnotation) -> Self {
307        Field::from(FieldMetadata::from(annotation))
308    }
309}
310
311impl From<FieldMetadata> for Field {
312    fn from(metadata: FieldMetadata) -> Self {
313        Field::from(SharedMetadata::from(metadata))
314    }
315}
316
317impl From<SharedMetadata> for Field {
318    fn from(metadata: SharedMetadata) -> Self {
319        Field {
320            metadata,
321            ..Default::default()
322        }
323    }
324}
325
326impl Field {
327    /// Map a function over the value of the field, if any.
328    pub fn map_value(self, f: impl FnOnce(NickelValue) -> NickelValue) -> Self {
329        Field {
330            value: self.value.map(f),
331            ..self
332        }
333    }
334
335    /// Map a fallible function over the value of the field, if any.
336    pub fn try_map_value<E>(
337        self,
338        f: impl FnOnce(NickelValue) -> Result<NickelValue, E>,
339    ) -> Result<Self, E> {
340        Ok(Field {
341            value: self.value.map(f).transpose()?,
342            ..self
343        })
344    }
345
346    /// Determine if a field is optional and without a defined value. In that case, it is usually
347    /// silently ignored by most record operations (`has_field`, `values`, etc.).
348    pub fn is_empty_optional(&self) -> bool {
349        self.value.is_none() && self.metadata.opt()
350    }
351
352    /// Required by the dynamic extension operator to know if the field being treated has a defined
353    /// value that must be obtained from the stack or not.
354    pub fn extension_kind(&self) -> RecordExtKind {
355        if self.value.is_some() {
356            RecordExtKind::WithValue
357        } else {
358            RecordExtKind::WithoutValue
359        }
360    }
361}
362
363impl Traverse<NickelValue> for Field {
364    fn traverse<F, E>(self, f: &mut F, order: TraverseOrder) -> Result<Field, E>
365    where
366        F: FnMut(NickelValue) -> Result<NickelValue, E>,
367    {
368        let mut metadata = self.metadata;
369
370        if let Some(rc) = metadata.0.as_mut() {
371            fallible_unique_map_in_place(rc, |m| {
372                let annotation = m.annotation.traverse(f, order)?;
373                Ok(FieldMetadata { annotation, ..m })
374            })?;
375        }
376
377        let value = self.value.map(|v| v.traverse(f, order)).transpose()?;
378
379        let pending_contracts = self
380            .pending_contracts
381            .into_iter()
382            .map(|pending_contract| pending_contract.traverse(f, order))
383            .collect::<Result<Vec<_>, _>>()?;
384
385        Ok(Field {
386            metadata,
387            value,
388            pending_contracts,
389        })
390    }
391
392    fn traverse_ref<S, U>(
393        &self,
394        f: &mut dyn FnMut(&NickelValue, &S) -> TraverseControl<S, U>,
395        state: &S,
396    ) -> Option<U> {
397        self.metadata
398            .0
399            .as_ref()
400            .and_then(|m| m.annotation.traverse_ref(f, state))
401            .or_else(|| self.value.as_ref().and_then(|v| v.traverse_ref(f, state)))
402            .or_else(|| {
403                self.pending_contracts
404                    .iter()
405                    .find_map(|c| c.traverse_ref(f, state))
406            })
407    }
408}
409
410/// The base structure of a Nickel record.
411///
412/// Used to group together fields common to both the [NickelValue] evaluated record and the
413/// [super::Term::RecRecord] term.
414#[derive(Clone, Debug, Default, PartialEq)]
415pub struct RecordData {
416    /// Fields whose names are known statically.
417    pub fields: IndexMap<LocIdent, Field>,
418    /// Attributes which may be applied to a record.
419    pub attrs: RecordAttrs,
420    /// The hidden part of a record under a polymorphic contract.
421    pub sealed_tail: Option<Rc<SealedTail>>,
422}
423
424/// Error raised by [RecordData] methods when trying to access a field that doesn't have a
425/// definition and isn't optional.
426#[derive(Clone, Debug)]
427pub struct MissingFieldDefErrorData {
428    pub id: LocIdent,
429    pub metadata: FieldMetadata,
430}
431
432pub type MissingFieldDefError = Box<MissingFieldDefErrorData>;
433
434impl MissingFieldDefErrorData {
435    pub fn into_eval_err(self, pos_record: PosIdx, pos_access: PosIdx) -> EvalErrorKind {
436        EvalErrorKind::MissingFieldDef {
437            id: self.id,
438            metadata: self.metadata,
439            pos_record,
440            pos_access,
441        }
442    }
443}
444
445impl RecordData {
446    pub fn new(
447        fields: IndexMap<LocIdent, Field>,
448        attrs: RecordAttrs,
449        sealed_tail: Option<SealedTail>,
450    ) -> Self {
451        RecordData {
452            fields,
453            attrs,
454            sealed_tail: sealed_tail.map(Rc::new),
455        }
456    }
457
458    /// Variant of [Self::new] that takes an `Option<Rc<_>>` for the sealed tail. This is useful
459    /// when re-using the tail from existing record data. Using [Self::new] instead would otherwise
460    /// require to clone the inner content and allocate a new `Rc`, which is wasteful.
461    pub fn new_shared_tail(
462        fields: IndexMap<LocIdent, Field>,
463        attrs: RecordAttrs,
464        sealed_tail: Option<Rc<SealedTail>>,
465    ) -> Self {
466        RecordData {
467            fields,
468            attrs,
469            sealed_tail,
470        }
471    }
472
473    /// A record with no fields and the default set of attributes.
474    pub fn empty() -> Self {
475        Default::default()
476    }
477
478    /// A record with the provided fields and the default set of attributes.
479    pub fn with_field_values(
480        field_values: impl IntoIterator<Item = (LocIdent, NickelValue)>,
481    ) -> Self {
482        let fields = field_values
483            .into_iter()
484            .map(|(id, value)| (id, Field::from(value)))
485            .collect();
486
487        RecordData {
488            fields,
489            ..Default::default()
490        }
491    }
492
493    /// Returns the record resulting from applying the provided function
494    /// to each field.
495    ///
496    /// Note that `f` is taken as `mut` in order to allow it to mutate
497    /// external state while iterating.
498    pub fn map_values<F>(self, mut f: F) -> Self
499    where
500        F: FnMut(LocIdent, Option<NickelValue>) -> Option<NickelValue>,
501    {
502        let fields = self
503            .fields
504            .into_iter()
505            .map(|(id, field)| {
506                (
507                    id,
508                    Field {
509                        value: f(id, field.value),
510                        ..field
511                    },
512                )
513            })
514            .collect();
515        RecordData { fields, ..self }
516    }
517
518    /// Returns the record resulting from applying the provided function to each field with a
519    /// defined value. Fields without a value are left unchanged.
520    pub fn map_defined_values<F>(self, mut f: F) -> Self
521    where
522        F: FnMut(LocIdent, NickelValue) -> NickelValue,
523    {
524        self.map_values(|id, value| value.map(|v| f(id, v)))
525    }
526
527    /// Turn the record into an iterator over the fields' values, ignoring optional fields without
528    /// definition.
529    ///
530    /// The returned iterator applies pending contracts to each value.
531    ///
532    /// Fields that aren't optional but yet don't have a definition are mapped to the
533    /// error `MissingFieldDefError`.
534    pub fn iter_without_opts(
535        &self,
536    ) -> impl Iterator<Item = Result<(Ident, NickelValue), MissingFieldDefError>> {
537        self.fields
538            .iter()
539            .filter_map(|(id, field)| match &field.value {
540                Some(v) => {
541                    let pos = v.pos_idx();
542                    Some(Ok((
543                        id.ident(),
544                        RuntimeContract::apply_all(
545                            v.clone(),
546                            field.pending_contracts.iter().cloned(),
547                            pos,
548                        ),
549                    )))
550                }
551                None if !field.metadata.opt() => Some(Err(Box::new(MissingFieldDefErrorData {
552                    id: *id,
553                    metadata: field.metadata.clone_inner(),
554                }))),
555                None => None,
556            })
557    }
558
559    /// Return an iterator over the fields' values, ignoring optional fields
560    /// without definition and fields marked as not_exported. Fields that
561    /// aren't optional but yet don't have a definition are mapped to the error
562    /// `MissingFieldDefError`.
563    pub fn iter_serializable(
564        &self,
565    ) -> impl Iterator<Item = Result<(Ident, &NickelValue), MissingFieldDefError>> {
566        self.fields.iter().filter_map(|(id, field)| {
567            debug_assert!(field.pending_contracts.is_empty());
568            match field.value {
569                Some(ref v) if !field.metadata.not_exported() => Some(Ok((id.ident(), v))),
570                None if !field.metadata.opt() && !field.metadata.not_exported() => {
571                    Some(Err(Box::new(MissingFieldDefErrorData {
572                        id: *id,
573                        metadata: field.metadata.clone_inner(),
574                    })))
575                }
576                _ => None,
577            }
578        })
579    }
580
581    /// Get the value of a field. Ignore optional fields without value: trying to get their value
582    /// returns `None`, as if they weren't present at all. Trying to extract a field without value
583    /// which is non optional return an error.
584    ///
585    /// This method automatically applies the potential pending contracts
586    pub fn get_value_with_ctrs(
587        &self,
588        id: &LocIdent,
589    ) -> Result<Option<NickelValue>, MissingFieldDefError> {
590        match self.fields.get(id) {
591            Some(Field {
592                value: None,
593                metadata,
594                ..
595            }) if !metadata.opt() => Err(Box::new(MissingFieldDefErrorData {
596                id: *id,
597                metadata: metadata.clone_inner(),
598            })),
599            Some(Field {
600                value: Some(value),
601                pending_contracts,
602                ..
603            }) => {
604                let pos = value.pos_idx();
605                Ok(Some(RuntimeContract::apply_all(
606                    value.clone(),
607                    pending_contracts.iter().cloned(),
608                    pos,
609                )))
610            }
611            _ => Ok(None),
612        }
613    }
614
615    /// Return a vector of all the fields' names of this record sorted alphabetically.
616    ///
617    /// # Parameters
618    ///
619    /// - `op_kind` controls if we should ignore or include empty optional fields
620    pub fn field_names(&self, op_kind: RecordOpKind) -> Vec<LocIdent> {
621        let mut fields: Vec<LocIdent> = self
622            .fields
623            .iter()
624            // Ignore optional fields without definitions.
625            .filter(|(_, field)| {
626                matches!(op_kind, RecordOpKind::ConsiderAllFields) || !field.is_empty_optional()
627            })
628            .map(|(id, _)| *id)
629            .collect();
630
631        fields.sort_by(|id1, id2| id1.label().cmp(id2.label()));
632        fields
633    }
634
635    /// Checks if this record is empty (including the sealed tail). Whether the record is open or
636    /// not doesn't impact emptiness: `{..}` is considered empty.
637    pub fn is_empty(&self) -> bool {
638        self.fields.is_empty() && self.sealed_tail.is_none()
639    }
640
641    /// Checks if this record is empty (including the sealed tail), or if it is composed only of
642    /// empty optional fields. [Self::is_empty] implies [Self::has_only_empty_opts], but the
643    /// converse is not true, typically for `{foo | optional}`, for example.
644    pub fn has_only_empty_opts(&self) -> bool {
645        self.fields.values().all(Field::is_empty_optional) && self.sealed_tail.is_none()
646    }
647}
648
649/// The sealed tail of a Nickel record under a polymorphic contract.
650///
651/// Note that access to the enclosed term must only be allowed when a matching sealing key is
652/// provided. If this is not enforced it will lead to parametricity violations.
653#[derive(Clone, Debug, PartialEq)]
654pub struct SealedTail {
655    /// The key with which the tail is sealed.
656    sealing_key: SealingKey,
657    /// The label to which blame will be attributed if code tries to
658    /// interact with the sealed tail in any way.
659    pub label: Label,
660    /// The term which is sealed.
661    term: NickelValue,
662    /// The field names of the sealed fields.
663    // You may find yourself wondering why this is a `Vec` rather than a
664    // `HashSet` given we only ever do containment checks against it.
665    // In brief: we'd need to use a `HashSet<String>`, which would mean
666    // allocating `fields.len()` `String`s in a fairly hot codepath.
667    // Since we only ever check whether the tail contains a specific field
668    // when we already know we're going to raise an error, it's not really
669    // an issue to have a linear lookup there, so we do that instead.
670    fields: Vec<Ident>,
671}
672
673impl SealedTail {
674    pub fn new(
675        sealing_key: SealingKey,
676        label: Label,
677        term: NickelValue,
678        fields: Vec<Ident>,
679    ) -> Self {
680        Self {
681            sealing_key,
682            label,
683            term,
684            fields,
685        }
686    }
687
688    /// Returns the sealed term if the key matches, otherwise returns None.
689    pub fn unseal(&self, key: &SealingKey) -> Option<&NickelValue> {
690        if key == &self.sealing_key {
691            Some(&self.term)
692        } else {
693            None
694        }
695    }
696
697    pub fn has_field(&self, field: &Ident) -> bool {
698        self.fields.contains(field)
699    }
700
701    pub fn has_dyn_field(&self, field: &str) -> bool {
702        self.fields.iter().any(|i| i.label() == field)
703    }
704}