nickel_lang_core/bytecode/ast/pattern/
bindings.rs

1//! Pattern analysis. The trait defined here is mostly used by the LSP.
2
3use crate::{
4    bytecode::ast::{pattern::*, record::FieldMetadata},
5    identifier::LocIdent,
6};
7
8/// A variable bound in a pattern, together with the path to the field it matches and the
9/// associated extra annotations.
10pub struct PatBinding<'ast> {
11    pub path: Vec<LocIdent>,
12    pub id: LocIdent,
13    pub metadata: FieldMetadata<'ast>,
14}
15
16pub trait Bindings<'ast> {
17    /// Returns a list of all variables bound by this pattern, together with the path to the field
18    /// they match and the associated extra annotations.
19    ///
20    /// # Example
21    ///
22    /// For a pattern `{a = x @ {foo, bar | Number = z, ..rest}, d = e}`, the result of this function
23    /// contains:
24    ///
25    /// - `(["a"], "x", empty field)` for the `x` variable
26    /// - `(["a"], "rest", empty field)` for the `rest` variable
27    /// - `(["a", "foo"], "foo", empty field)` for the `foo` variable
28    /// - `(["a", "bar"], "z", field with Number contract)` for the `z` variable
29    /// - `(["d"], "e", empty field)` for the `e` variable
30    fn bindings(&self) -> Vec<PatBinding<'ast>>;
31}
32
33trait InjectBindings<'ast> {
34    /// Same as [Bindings::bindings], but work relative to a current path inside a pattern and
35    /// injects the bindings into a working vector instead of returning the result. This method is
36    /// mostly used internally and is the one performing the actual work.
37    ///
38    /// Other modules of the LSP should use [Bindings::bindings] directly.
39    ///
40    /// # Parameters
41    ///
42    /// - `bindings`: the vector to inject the bindings into.
43    /// - `path`: the field path to the sub-pattern being analysed.
44    /// - `parent_extra`: the extra annotations associated with a potential parent field pattern.
45    ///   For example, when injecting the bindings of `{foo ? 5 = x @ y @ z}`, all the introduced
46    ///   variables should refer to default annotation of `foo`. This annotation is thus passed
47    ///   along when calling to the sub-patterns' [Self::inject_bindings].
48    fn inject_bindings(
49        &self,
50        bindings: &mut Vec<PatBinding<'ast>>,
51        path: Vec<LocIdent>,
52        parent_extra: Option<&FieldMetadata<'ast>>,
53    );
54}
55
56impl<'ast> Bindings<'ast> for Pattern<'ast> {
57    fn bindings(&self) -> Vec<PatBinding<'ast>> {
58        let mut bindings = Vec::new();
59        self.inject_bindings(&mut bindings, Vec::new(), None);
60        bindings
61    }
62}
63
64impl<'ast> InjectBindings<'ast> for Pattern<'ast> {
65    fn inject_bindings(
66        &self,
67        bindings: &mut Vec<PatBinding<'ast>>,
68        path: Vec<LocIdent>,
69        parent_deco: Option<&FieldMetadata<'ast>>,
70    ) {
71        if let Some(alias) = self.alias {
72            bindings.push(PatBinding {
73                path: path.clone(),
74                id: alias,
75                metadata: parent_deco.cloned().unwrap_or_default(),
76            });
77        }
78
79        self.data.inject_bindings(bindings, path, parent_deco);
80    }
81}
82
83impl<'ast> InjectBindings<'ast> for PatternData<'ast> {
84    fn inject_bindings(
85        &self,
86        bindings: &mut Vec<PatBinding<'ast>>,
87        path: Vec<LocIdent>,
88        parent_deco: Option<&FieldMetadata<'ast>>,
89    ) {
90        match self {
91            PatternData::Any(id) => bindings.push(PatBinding {
92                path,
93                id: *id,
94                metadata: parent_deco.cloned().unwrap_or_default(),
95            }),
96            PatternData::Record(record_pat) => {
97                record_pat.inject_bindings(bindings, path, parent_deco)
98            }
99            PatternData::Array(array_pat) => array_pat.inject_bindings(bindings, path, parent_deco),
100            PatternData::Enum(evariant_pat) => {
101                evariant_pat.inject_bindings(bindings, path, parent_deco)
102            }
103            PatternData::Or(or_pat) => or_pat.inject_bindings(bindings, path, parent_deco),
104            // Wildcard and constant patterns don't bind any variable
105            PatternData::Wildcard | PatternData::Constant(_) => (),
106        }
107    }
108}
109
110impl<'ast> InjectBindings<'ast> for RecordPattern<'ast> {
111    fn inject_bindings(
112        &self,
113        bindings: &mut Vec<PatBinding<'ast>>,
114        path: Vec<LocIdent>,
115        parent_extra: Option<&FieldMetadata<'ast>>,
116    ) {
117        for field_pat in self.patterns.iter() {
118            // Field patterns have their own annotation, so there's no need to propagate
119            // `parent_extra` any further
120            field_pat.inject_bindings(bindings, path.clone(), None);
121        }
122
123        if let TailPattern::Capture(rest) = self.tail {
124            // If a contract is attached to the whole record pattern in the enclosing pattern, the
125            // rest doesn't exactly match this contract: there are some fields missing. Still, it
126            // sounds more useful to keep the whole metadata - including documentation - for
127            // autocompletion and the like, even if it's an over-approximation.
128            bindings.push(PatBinding {
129                path,
130                id: rest,
131                metadata: parent_extra.cloned().unwrap_or_default(),
132            });
133        }
134    }
135}
136
137impl<'ast> InjectBindings<'ast> for ArrayPattern<'ast> {
138    fn inject_bindings(
139        &self,
140        bindings: &mut Vec<PatBinding<'ast>>,
141        path: Vec<LocIdent>,
142        _parent_extra: Option<&FieldMetadata<'ast>>,
143    ) {
144        for subpat in self.patterns.iter() {
145            // Array elements shouldn't inherit the annotation from their parent (for once, they
146            // are of a different type), so we reset `parent_extra` to `None`.
147            subpat.inject_bindings(bindings, path.clone(), None);
148        }
149
150        if let TailPattern::Capture(rest) = self.tail {
151            bindings.push(PatBinding {
152                path,
153                id: rest,
154                metadata: Default::default(),
155            });
156        }
157    }
158}
159
160impl<'ast> InjectBindings<'ast> for FieldPattern<'ast> {
161    fn inject_bindings(
162        &self,
163        bindings: &mut Vec<PatBinding<'ast>>,
164        mut path: Vec<LocIdent>,
165        _parent_extra: Option<&FieldMetadata<'ast>>,
166    ) {
167        path.push(self.matched_id);
168        self.pattern.inject_bindings(
169            bindings,
170            path,
171            Some(&FieldMetadata::from(self.annotation.clone())),
172        );
173    }
174}
175
176impl<'ast> InjectBindings<'ast> for EnumPattern<'ast> {
177    fn inject_bindings(
178        &self,
179        bindings: &mut Vec<PatBinding<'ast>>,
180        path: Vec<LocIdent>,
181        _parent_extra: Option<&FieldMetadata<'ast>>,
182    ) {
183        //TODO: I'm not sure we should just transparently forward to the variant's argument. Maybe
184        //we need a more complex notion of path here, that knows when we enter an enum variant?
185        if let Some(ref arg_pat) = self.pattern {
186            arg_pat.inject_bindings(bindings, path, None);
187        }
188    }
189}
190
191impl<'ast> InjectBindings<'ast> for OrPattern<'ast> {
192    fn inject_bindings(
193        &self,
194        bindings: &mut Vec<PatBinding<'ast>>,
195        path: Vec<LocIdent>,
196        parent_extra: Option<&FieldMetadata<'ast>>,
197    ) {
198        for subpat in self.patterns.iter() {
199            subpat.inject_bindings(bindings, path.clone(), parent_extra);
200        }
201    }
202}