Skip to main content

boa_ast/declaration/
variable.rs

1//! Variable related declarations.
2
3use super::Declaration;
4use crate::{
5    Statement,
6    expression::{Expression, Identifier},
7    join_nodes,
8    pattern::Pattern,
9    visitor::{VisitWith, Visitor, VisitorMut},
10};
11use boa_interner::{Interner, ToInternedString};
12use core::{convert::TryFrom, fmt::Write as _, ops::ControlFlow};
13
14/// A [`var`][var] statement, also called [`VariableStatement`][varstmt] in the spec.
15///
16/// The scope of a variable declared with `var` is its current execution context, which is either
17/// the enclosing function or, for variables declared outside any function, global. If you
18/// re-declare a ECMAScript variable, it will not lose its value.
19///
20/// Although a bit confusing, `VarDeclaration`s are not considered [`Declaration`]s by the spec.
21/// This is partly because it has very different semantics from `let` and `const` declarations, but
22/// also because a `var` statement can be labelled just like any other [`Statement`]:
23///
24/// ```javascript
25/// label: var a = 5;
26/// a;
27/// ```
28///
29/// returns `5` as the value of the statement list, while:
30///
31/// ```javascript
32/// label: let a = 5;
33/// a;
34/// ```
35/// throws a `SyntaxError`.
36///
37/// `var` declarations, wherever they occur, are processed before any code is executed. This is
38/// called <code>[hoisting]</code>.
39///
40/// [var]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/var
41/// [varstmt]: https://tc39.es/ecma262/#prod-VariableStatement
42/// [hoisting]: https://developer.mozilla.org/en-US/docs/Glossary/Hoisting
43#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
44#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
45#[derive(Clone, Debug, PartialEq)]
46pub struct VarDeclaration(pub VariableList);
47
48impl From<VarDeclaration> for Statement {
49    fn from(var: VarDeclaration) -> Self {
50        Self::Var(var)
51    }
52}
53
54impl ToInternedString for VarDeclaration {
55    fn to_interned_string(&self, interner: &Interner) -> String {
56        format!("var {}", self.0.to_interned_string(interner))
57    }
58}
59
60impl VisitWith for VarDeclaration {
61    fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
62    where
63        V: Visitor<'a>,
64    {
65        visitor.visit_variable_list(&self.0)
66    }
67
68    fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
69    where
70        V: VisitorMut<'a>,
71    {
72        visitor.visit_variable_list_mut(&mut self.0)
73    }
74}
75
76/// A **[lexical declaration]** defines variables that are scoped to the lexical environment of
77/// the variable declaration.
78///
79/// [lexical declaration]: https://tc39.es/ecma262/#sec-let-and-const-declarations
80#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
81#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
82#[derive(Clone, Debug, PartialEq)]
83pub enum LexicalDeclaration {
84    /// A <code>[const]</code> variable creates a constant whose scope can be either global or local
85    /// to the block in which it is declared.
86    ///
87    /// An initializer for a constant is required. You must specify its value in the same statement
88    /// in which it's declared. (This makes sense, given that it can't be changed later)
89    ///
90    /// [const]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/const
91    Const(VariableList),
92
93    /// A <code>[let]</code> variable is limited to a scope of a block statement, or expression on
94    /// which it is used, unlike the `var` keyword, which defines a variable globally, or locally to
95    /// an entire function regardless of block scope.
96    ///
97    /// Just like const, `let` does not create properties of the window object when declared
98    /// globally (in the top-most scope).
99    ///
100    /// If a let declaration does not have an initializer, the variable is assigned the value `undefined`.
101    ///
102    /// [let]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let
103    Let(VariableList),
104
105    /// A <code>[using]</code> declaration creates a block-scoped resource that is automatically
106    /// disposed when control exits the block.
107    ///
108    /// [using]: https://tc39.es/proposal-explicit-resource-management/
109    Using(VariableList),
110
111    /// An <code>[await using]</code> declaration creates a block-scoped resource that is automatically
112    /// disposed asynchronously when control exits the block.
113    ///
114    /// [await using]: https://tc39.es/proposal-explicit-resource-management/
115    AwaitUsing(VariableList),
116}
117
118impl LexicalDeclaration {
119    /// Gets the inner variable list of the `LexicalDeclaration`
120    #[must_use]
121    pub const fn variable_list(&self) -> &VariableList {
122        match self {
123            Self::Const(list) | Self::Let(list) | Self::Using(list) | Self::AwaitUsing(list) => {
124                list
125            }
126        }
127    }
128
129    /// Returns `true` if the declaration is a `const` declaration.
130    #[must_use]
131    pub const fn is_const(&self) -> bool {
132        matches!(self, Self::Const(_))
133    }
134}
135
136impl From<LexicalDeclaration> for Declaration {
137    fn from(lex: LexicalDeclaration) -> Self {
138        Self::Lexical(lex)
139    }
140}
141
142impl ToInternedString for LexicalDeclaration {
143    fn to_interned_string(&self, interner: &Interner) -> String {
144        format!(
145            "{} {}",
146            match &self {
147                Self::Let(_) => "let",
148                Self::Const(_) => "const",
149                Self::Using(_) => "using",
150                Self::AwaitUsing(_) => "await using",
151            },
152            self.variable_list().to_interned_string(interner)
153        )
154    }
155}
156
157impl VisitWith for LexicalDeclaration {
158    fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
159    where
160        V: Visitor<'a>,
161    {
162        match self {
163            Self::Const(vars) | Self::Let(vars) | Self::Using(vars) | Self::AwaitUsing(vars) => {
164                visitor.visit_variable_list(vars)
165            }
166        }
167    }
168
169    fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
170    where
171        V: VisitorMut<'a>,
172    {
173        match self {
174            Self::Const(vars) | Self::Let(vars) | Self::Using(vars) | Self::AwaitUsing(vars) => {
175                visitor.visit_variable_list_mut(vars)
176            }
177        }
178    }
179}
180
181/// List of variables in a variable declaration.
182#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
183#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
184#[derive(Clone, Debug, PartialEq)]
185pub struct VariableList {
186    list: Box<[Variable]>,
187}
188
189impl VariableList {
190    /// Creates a variable list if the provided list of [`Variable`] is not empty.
191    #[must_use]
192    pub fn new(list: Box<[Variable]>) -> Option<Self> {
193        if list.is_empty() {
194            return None;
195        }
196
197        Some(Self { list })
198    }
199}
200
201impl AsRef<[Variable]> for VariableList {
202    fn as_ref(&self) -> &[Variable] {
203        &self.list
204    }
205}
206
207impl ToInternedString for VariableList {
208    fn to_interned_string(&self, interner: &Interner) -> String {
209        join_nodes(interner, self.list.as_ref())
210    }
211}
212
213impl VisitWith for VariableList {
214    fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
215    where
216        V: Visitor<'a>,
217    {
218        for variable in &*self.list {
219            visitor.visit_variable(variable)?;
220        }
221        ControlFlow::Continue(())
222    }
223
224    fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
225    where
226        V: VisitorMut<'a>,
227    {
228        for variable in &mut *self.list {
229            visitor.visit_variable_mut(variable)?;
230        }
231        ControlFlow::Continue(())
232    }
233}
234
235/// The error returned by the [`VariableList::try_from`] function.
236#[derive(Debug, Copy, Clone, PartialEq, Eq)]
237pub struct TryFromVariableListError(());
238
239impl std::fmt::Display for TryFromVariableListError {
240    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
241        "provided list of variables cannot be empty".fmt(f)
242    }
243}
244
245impl TryFrom<Box<[Variable]>> for VariableList {
246    type Error = TryFromVariableListError;
247
248    fn try_from(value: Box<[Variable]>) -> Result<Self, Self::Error> {
249        Self::new(value).ok_or(TryFromVariableListError(()))
250    }
251}
252
253impl TryFrom<Vec<Variable>> for VariableList {
254    type Error = TryFromVariableListError;
255
256    fn try_from(value: Vec<Variable>) -> Result<Self, Self::Error> {
257        Self::try_from(value.into_boxed_slice())
258    }
259}
260
261/// Variable represents a variable declaration of some kind.
262///
263/// For `let` and `const` declarations this type represents a [`LexicalBinding`][spec1]
264///
265/// For `var` declarations this type represents a [`VariableDeclaration`][spec2]
266///
267/// More information:
268///  - [ECMAScript reference: 14.3 Declarations and the Variable Statement][spec3]
269///
270/// [spec1]: https://tc39.es/ecma262/#prod-LexicalBinding
271/// [spec2]: https://tc39.es/ecma262/#prod-VariableDeclaration
272/// [spec3]:  https://tc39.es/ecma262/#sec-declarations-and-the-variable-statement
273#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
274#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
275#[derive(Clone, Debug, PartialEq)]
276pub struct Variable {
277    binding: Binding,
278    init: Option<Expression>,
279}
280
281impl ToInternedString for Variable {
282    fn to_interned_string(&self, interner: &Interner) -> String {
283        let mut buf = self.binding.to_interned_string(interner);
284
285        if let Some(ref init) = self.init {
286            let _ = write!(buf, " = {}", init.to_interned_string(interner));
287        }
288        buf
289    }
290}
291
292impl Variable {
293    /// Creates a new variable declaration from a `BindingIdentifier`.
294    #[inline]
295    #[must_use]
296    pub const fn from_identifier(ident: Identifier, init: Option<Expression>) -> Self {
297        Self {
298            binding: Binding::Identifier(ident),
299            init,
300        }
301    }
302
303    /// Creates a new variable declaration from a `Pattern`.
304    #[inline]
305    #[must_use]
306    pub const fn from_pattern(pattern: Pattern, init: Option<Expression>) -> Self {
307        Self {
308            binding: Binding::Pattern(pattern),
309            init,
310        }
311    }
312    /// Gets the variable declaration binding.
313    #[must_use]
314    pub const fn binding(&self) -> &Binding {
315        &self.binding
316    }
317
318    /// Gets the initialization expression for the variable declaration, if any.
319    #[inline]
320    #[must_use]
321    pub const fn init(&self) -> Option<&Expression> {
322        self.init.as_ref()
323    }
324}
325
326impl VisitWith for Variable {
327    fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
328    where
329        V: Visitor<'a>,
330    {
331        visitor.visit_binding(&self.binding)?;
332        if let Some(init) = &self.init {
333            visitor.visit_expression(init)?;
334        }
335        ControlFlow::Continue(())
336    }
337
338    fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
339    where
340        V: VisitorMut<'a>,
341    {
342        visitor.visit_binding_mut(&mut self.binding)?;
343        if let Some(init) = &mut self.init {
344            visitor.visit_expression_mut(init)?;
345        }
346        ControlFlow::Continue(())
347    }
348}
349
350/// Binding represents either an individual binding or a binding pattern.
351///
352/// More information:
353///  - [ECMAScript reference: 14.3 Declarations and the Variable Statement][spec]
354///
355/// [spec]:  https://tc39.es/ecma262/#sec-declarations-and-the-variable-statement
356#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
357#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
358#[derive(Clone, Debug, PartialEq)]
359pub enum Binding {
360    /// A single identifier binding.
361    Identifier(Identifier),
362    /// A pattern binding.
363    Pattern(Pattern),
364}
365
366impl From<Identifier> for Binding {
367    fn from(id: Identifier) -> Self {
368        Self::Identifier(id)
369    }
370}
371
372impl From<Pattern> for Binding {
373    fn from(pat: Pattern) -> Self {
374        Self::Pattern(pat)
375    }
376}
377
378impl ToInternedString for Binding {
379    fn to_interned_string(&self, interner: &Interner) -> String {
380        match self {
381            Self::Identifier(id) => id.to_interned_string(interner),
382            Self::Pattern(pattern) => pattern.to_interned_string(interner),
383        }
384    }
385}
386
387impl VisitWith for Binding {
388    fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
389    where
390        V: Visitor<'a>,
391    {
392        match self {
393            Self::Identifier(id) => visitor.visit_identifier(id),
394            Self::Pattern(pattern) => visitor.visit_pattern(pattern),
395        }
396    }
397
398    fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
399    where
400        V: VisitorMut<'a>,
401    {
402        match self {
403            Self::Identifier(id) => visitor.visit_identifier_mut(id),
404            Self::Pattern(pattern) => visitor.visit_pattern_mut(pattern),
405        }
406    }
407}