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}