Skip to main content

turbopack_ecmascript/analyzer/jsvalue/
normalize.rs

1use std::{hash::BuildHasherDefault, mem::take};
2
3use rustc_hash::FxHasher;
4use turbo_tasks::FxIndexSet;
5
6use crate::analyzer::{Bump, BumpVec, JsValue, jsvalue::similar::SimilarJsValue};
7
8// Alternatives management
9impl<'a> JsValue<'a> {
10    /// Add an alternative to the current value. Might be a no-op if the value
11    /// already contains this alternative. Potentially expensive operation
12    /// as it has to compare the value with all existing alternatives.
13    pub(crate) fn add_alt(&mut self, arena: &'a Bump, v: Self) {
14        if self == &v {
15            return;
16        }
17
18        if let JsValue::Alternatives {
19            total_nodes: c,
20            values,
21            logical_property: _,
22        } = self
23        {
24            if !values.contains(&v) {
25                *c += v.total_nodes();
26                values.push(arena, v);
27            }
28        } else {
29            let l = take(self);
30            *self = JsValue::Alternatives {
31                total_nodes: 1 + l.total_nodes() + v.total_nodes(),
32                values: BumpVec::from_iter_in(arena, [l, v]),
33                logical_property: None,
34            };
35        }
36    }
37}
38
39// Normalization
40impl<'a> JsValue<'a> {
41    /// Normalizes only the current node. Nested alternatives, concatenations,
42    /// or operations are collapsed.
43    pub fn normalize_shallow(&mut self, arena: &'a Bump) {
44        match self {
45            JsValue::Alternatives {
46                total_nodes: _,
47                values,
48                logical_property: _,
49            } => {
50                if values.len() == 1 {
51                    *self = take(&mut values[0]);
52                } else {
53                    let mut set = FxIndexSet::with_capacity_and_hasher(
54                        values.len(),
55                        BuildHasherDefault::<FxHasher>::default(),
56                    );
57                    // Take the children out so we can rebuild `values` in place.
58                    let taken = take(values);
59                    for v in taken {
60                        match v {
61                            JsValue::Alternatives {
62                                total_nodes: _,
63                                values,
64                                logical_property: _,
65                            } => {
66                                for v in values {
67                                    set.insert(SimilarJsValue(v));
68                                }
69                            }
70                            v => {
71                                set.insert(SimilarJsValue(v));
72                            }
73                        }
74                    }
75                    if set.len() == 1 {
76                        *self = set.into_iter().next().unwrap().0;
77                    } else {
78                        *values = BumpVec::from_iter_in(arena, set.into_iter().map(|v| v.0));
79                        self.update_total_nodes();
80                    }
81                }
82            }
83            JsValue::Promise(_, inner) | JsValue::Awaited(_, inner) => {
84                if resolve_promises(inner) {
85                    self.update_total_nodes();
86                }
87            }
88            JsValue::Concat(_, v) => {
89                // TODO(kdy1): Remove duplicate
90                let taken = take(v);
91                let mut new: BumpVec<JsValue> = BumpVec::with_capacity_in(arena, taken.len());
92                for v in taken {
93                    // Remove empty strings
94                    if v.as_str() == Some("") {
95                        continue;
96                    }
97                    if let Some(str) = v.as_str() {
98                        if let Some(last) = new.last_mut() {
99                            if let Some(last_str) = last.as_str() {
100                                *last = [last_str, str].concat().into();
101                            } else {
102                                new.push(arena, v);
103                            }
104                        } else {
105                            new.push(arena, v);
106                        }
107                    } else if let JsValue::Concat(_, v) = v {
108                        new.extend(arena, v);
109                    } else {
110                        new.push(arena, v);
111                    }
112                }
113                if new.len() == 1 {
114                    *self = new.into_iter().next().unwrap();
115                } else {
116                    *v = new;
117                    self.update_total_nodes();
118                }
119            }
120            JsValue::Add(_, v) => {
121                let taken = take(v);
122                let mut added: BumpVec<JsValue> = BumpVec::with_capacity_in(arena, taken.len());
123                let mut iter = taken.into_iter();
124                while let Some(item) = iter.next() {
125                    if item.is_string() == Some(true) {
126                        let mut concat: BumpVec<JsValue> = match added.len() {
127                            0 => BumpVec::new(),
128                            1 => BumpVec::from_iter_in(arena, [added.into_iter().next().unwrap()]),
129                            _ => BumpVec::from_iter_in(
130                                arena,
131                                [JsValue::Add(
132                                    1 + added.iter().map(|v| v.total_nodes()).sum::<u32>(),
133                                    added,
134                                )],
135                            ),
136                        };
137                        concat.push(arena, item);
138                        concat.extend(arena, iter);
139                        *self = JsValue::Concat(
140                            1 + concat.iter().map(|v| v.total_nodes()).sum::<u32>(),
141                            concat,
142                        );
143                        return;
144                    } else {
145                        added.push(arena, item);
146                    }
147                }
148                if added.len() == 1 {
149                    *self = added.into_iter().next().unwrap();
150                } else {
151                    *v = added;
152                    self.update_total_nodes();
153                }
154            }
155            JsValue::Logical(_, op, list)
156                // Nested logical expressions can be normalized: e. g. `a && (b && c)` => `a &&
157                // b && c`
158                if list.iter().any(|v| {
159                    if let JsValue::Logical(_, inner_op, _) = v {
160                        inner_op == op
161                    } else {
162                        false
163                    }
164                }) => {
165                    // Taking the old list and constructing a new merged list
166                    let taken = take(list);
167                    for mut v in taken {
168                        if let JsValue::Logical(_, inner_op, inner_list) = &mut v {
169                            if inner_op == op {
170                                list.extend(arena, take(inner_list));
171                            } else {
172                                list.push(arena, v);
173                            }
174                        } else {
175                            list.push(arena, v);
176                        }
177                    }
178                    self.update_total_nodes();
179                }
180            _ => {}
181        }
182    }
183
184    /// Normalizes the current node and all nested nodes.
185    pub fn normalize(&mut self, arena: &'a Bump) {
186        self.for_each_children_mut(&mut |child| {
187            child.normalize(arena);
188            true
189        });
190        self.normalize_shallow(arena);
191    }
192}
193
194/// Replaces `value` with what awaiting it produces, returning whether it changed.
195///
196/// ```text
197/// Promise<Promise<null>>       -> null
198/// Promise<null | Promise<0>>   -> null | 0
199/// c ? Promise<null> : Promise<0> -> c ? null : 0
200/// null                         -> null
201/// ```
202pub(crate) fn resolve_promises<'a>(value: &mut JsValue<'a>) -> bool {
203    match value {
204        JsValue::Promise(_, inner) => {
205            let mut inner = take(&mut **inner);
206            resolve_promises(&mut inner);
207            *value = inner;
208            true
209        }
210        // Awaiting a branching value awaits whichever branch is taken, so the promises are inside
211        // the branches rather than around them.
212        JsValue::Tenary(_, _, cons, alt) => {
213            let modified = resolve_promises(cons) | resolve_promises(alt);
214            if modified {
215                value.update_total_nodes();
216            }
217            modified
218        }
219        JsValue::Alternatives { values, .. } => {
220            let mut modified = false;
221            for alternative in values.iter_mut() {
222                modified |= resolve_promises(alternative);
223            }
224            if modified {
225                let values = take(values);
226                *value = JsValue::alternatives(values);
227            }
228            modified
229        }
230        _ => false,
231    }
232}
233
234// Similarity