trustfall_core/frontend/
tags.rs

1use std::{
2    collections::{BTreeMap, BTreeSet},
3    fmt::Debug,
4};
5
6use super::util::ComponentPath;
7use crate::{
8    ir::{FieldRef, Vid},
9    util::{BTreeMapOccupiedError, BTreeMapTryInsertExt},
10};
11
12#[derive(Debug, Default)]
13pub(super) struct TagHandler<'a> {
14    tags: BTreeMap<&'a str, TagEntry<'a>>,
15    used_tags: BTreeSet<&'a str>,
16    component_imported_tags: Vec<(Vid, Vec<FieldRef>)>,
17}
18
19#[derive(Debug, Clone)]
20pub(super) struct TagEntry<'a> {
21    pub(super) name: &'a str,
22    pub(super) field: FieldRef,
23    pub(super) path: ComponentPath,
24}
25
26impl<'a> TagEntry<'a> {
27    fn new(name: &'a str, field: FieldRef, path: ComponentPath) -> Self {
28        Self { name, field, path }
29    }
30}
31
32impl<'a> TagHandler<'a> {
33    #[inline]
34    pub(super) fn new() -> Self {
35        Default::default()
36    }
37
38    pub(super) fn register_tag(
39        &mut self,
40        name: &'a str,
41        field: FieldRef,
42        path: &ComponentPath,
43    ) -> Result<(), BTreeMapOccupiedError<'_, &'a str, TagEntry<'a>>> {
44        self.tags.insert_or_error(name, TagEntry::new(name, field, path.clone()))?;
45
46        Ok(())
47    }
48
49    pub(super) fn begin_subcomponent(&mut self, component_root: Vid) {
50        self.component_imported_tags.push((component_root, vec![]));
51    }
52
53    pub(super) fn end_subcomponent(&mut self, component_root: Vid) -> Vec<FieldRef> {
54        let (expected_vid, external_tags) = self.component_imported_tags.pop().unwrap();
55        assert_eq!(expected_vid, component_root);
56        external_tags
57    }
58
59    pub(super) fn reference_tag(
60        &mut self,
61        name: &str,
62        use_path: &ComponentPath,
63        use_vid: Vid,
64    ) -> Result<&TagEntry<'_>, TagLookupError> {
65        let entry =
66            self.tags.get(name).ok_or_else(|| TagLookupError::UndefinedTag(name.to_string()))?;
67
68        if entry.path.is_parent(use_path) {
69            if entry.field.defined_at() > use_vid {
70                return Err(TagLookupError::TagUsedBeforeDefinition(name.to_string()));
71            }
72
73            if &entry.path != use_path {
74                // The tag is used inside a fold and imported from an outer component.
75                // Mark it as imported at the appropriate level.
76                let importing_component_root = use_path[entry.path.len()];
77
78                // The -1 in the index calculation is because the root component
79                // cannot import tags -- it has no parent component to import from.
80                let (component_root, imported_tags) =
81                    self.component_imported_tags.get_mut(entry.path.len() - 1).unwrap();
82                assert_eq!(*component_root, importing_component_root);
83                imported_tags.push(entry.field.clone());
84            }
85
86            self.used_tags.insert(entry.name);
87            Ok(entry)
88        } else {
89            // The tag is defined in a fold that is either inside of, or parallel to,
90            // the component that uses the tag. This is not allowed.
91            Err(TagLookupError::TagDefinedInsideFold(name.to_string()))
92        }
93    }
94
95    pub(super) fn finish(self) -> Result<(), BTreeSet<&'a str>> {
96        let unused_tags: BTreeSet<_> =
97            self.tags.keys().copied().filter(|x| !self.used_tags.contains(x)).collect();
98        if unused_tags.is_empty() {
99            Ok(())
100        } else {
101            Err(unused_tags)
102        }
103    }
104}
105
106pub(super) enum TagLookupError {
107    UndefinedTag(String),
108    TagUsedBeforeDefinition(String),
109    TagDefinedInsideFold(String),
110}