1use std::{cmp::Ordering, collections::hash_map, fmt::Debug, ops::Deref};
2
3use rspack_cacheable::{
4 cacheable,
5 with::{AsRefStr, AsVec},
6};
7#[cfg(allocative)]
8use rspack_util::allocative;
9use rustc_hash::FxHashMap;
10use ustr::{Ustr, UstrSet};
11
12use crate::{EntryOptions, EntryRuntime};
13
14#[cacheable]
15#[derive(Debug, Default, Clone)]
16#[cfg_attr(allocative, derive(allocative::Allocative))]
17pub struct RuntimeSpec {
18 #[cacheable(with=AsVec<AsRefStr>)]
19 inner: UstrSet,
20 key: String,
21}
22
23impl std::fmt::Display for RuntimeSpec {
24 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25 let mut iter = self.iter();
26 if let Some(first) = iter.next() {
27 write!(f, "{first}")?;
28 }
29 for r in iter {
30 write!(f, ",")?;
31 write!(f, "{r}")?;
32 }
33 Ok(())
34 }
35}
36
37impl std::hash::Hash for RuntimeSpec {
38 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
39 self.key.hash(state);
40 }
41}
42
43impl std::cmp::PartialEq for RuntimeSpec {
44 fn eq(&self, other: &Self) -> bool {
45 self.key == other.key
46 }
47}
48
49impl std::cmp::Eq for RuntimeSpec {}
50
51impl Deref for RuntimeSpec {
52 type Target = UstrSet;
53
54 fn deref(&self) -> &Self::Target {
55 &self.inner
56 }
57}
58
59impl From<UstrSet> for RuntimeSpec {
60 fn from(value: UstrSet) -> Self {
61 Self::new(value)
62 }
63}
64
65impl FromIterator<Ustr> for RuntimeSpec {
66 fn from_iter<T: IntoIterator<Item = Ustr>>(iter: T) -> Self {
67 Self::new(UstrSet::from_iter(iter))
68 }
69}
70
71impl IntoIterator for RuntimeSpec {
72 type Item = Ustr;
73 type IntoIter = <UstrSet as IntoIterator>::IntoIter;
74
75 fn into_iter(self) -> Self::IntoIter {
76 self.inner.into_iter()
77 }
78}
79
80impl RuntimeSpec {
81 pub fn new(inner: UstrSet) -> Self {
82 let mut this = Self {
83 inner,
84 key: String::new(),
85 };
86 this.update_key();
87 this
88 }
89
90 pub fn from_entry(entry: &str, runtime: Option<&EntryRuntime>) -> Self {
91 let r = match runtime {
92 Some(EntryRuntime::String(s)) => s,
93 _ => entry,
94 }
95 .to_string();
96 Self::from_iter([r.into()])
97 }
98
99 pub fn from_entry_options(options: &EntryOptions) -> Option<Self> {
100 let r = match &options.runtime {
101 Some(EntryRuntime::String(s)) => Some(s.to_owned()),
102 _ => options.name.clone(),
103 };
104 r.map(|r| Self::from_iter([r.into()]))
105 }
106
107 pub fn subtract(&self, b: &RuntimeSpec) -> Self {
108 let res = self.inner.difference(&b.inner).copied().collect();
109 Self::new(res)
110 }
111
112 pub fn insert(&mut self, r: Ustr) -> bool {
113 let update = self.inner.insert(r);
114 if update {
115 self.update_key();
116 }
117 update
118 }
119
120 pub fn extend(&mut self, other: &Self) {
121 let prev = self.inner.len();
122 self.inner.extend(other.inner.iter().copied());
123 if prev != self.inner.len() {
124 self.update_key();
125 }
126 }
127
128 fn update_key(&mut self) {
129 if self.inner.is_empty() {
130 if self.key.is_empty() {
131 return;
132 }
133 self.key = String::new();
134 return;
135 }
136 let mut ordered = self.inner.iter().map(|s| s.as_str()).collect::<Vec<_>>();
137 ordered.sort_unstable();
138 self.key = ordered.join("_");
139 }
140
141 pub fn as_str(&self) -> &str {
142 &self.key
143 }
144}
145
146pub type RuntimeKey = String;
147
148pub type RuntimeKeyMap<T> = FxHashMap<RuntimeKey, T>;
149
150#[derive(Default, Clone, Copy, Debug, PartialEq, Eq)]
151pub enum RuntimeMode {
152 #[default]
153 Empty = 0,
154 SingleEntry = 1,
155 Map = 2,
156}
157
158pub fn is_runtime_equal(a: &RuntimeSpec, b: &RuntimeSpec) -> bool {
159 a.key == b.key
160}
161
162#[derive(Debug, Clone, PartialEq, Eq)]
163#[cfg_attr(allocative, derive(allocative::Allocative))]
164pub enum RuntimeCondition {
165 Boolean(bool),
166 Spec(RuntimeSpec),
167}
168
169impl std::hash::Hash for RuntimeCondition {
170 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
171 match self {
172 Self::Boolean(v) => v.hash(state),
173 Self::Spec(s) => {
174 for i in s.iter() {
175 i.hash(state);
176 }
177 }
178 }
179 }
180}
181
182impl RuntimeCondition {
183 pub fn as_spec(&self) -> Option<&RuntimeSpec> {
184 if let Self::Spec(v) = self {
185 Some(v)
186 } else {
187 None
188 }
189 }
190
191 pub fn as_spec_mut(&mut self) -> Option<&mut RuntimeSpec> {
192 if let Self::Spec(v) = self {
193 Some(v)
194 } else {
195 None
196 }
197 }
198}
199
200pub fn merge_runtime(a: &RuntimeSpec, b: &RuntimeSpec) -> RuntimeSpec {
201 let mut set = a.inner.clone();
202 set.extend(b.inner.iter().copied());
203 RuntimeSpec::new(set)
204}
205
206pub fn filter_runtime(
207 runtime: Option<&RuntimeSpec>,
208 filter: impl Fn(Option<&RuntimeSpec>) -> bool,
209) -> RuntimeCondition {
210 match runtime {
211 None => RuntimeCondition::Boolean(filter(None)),
212 Some(runtime) => {
213 let mut some = false;
214 let mut every = true;
215 let mut result = UstrSet::default();
216
217 for &r in runtime.iter() {
218 let cur = RuntimeSpec::from_iter([r]);
219 let v = filter(Some(&cur));
220 if v {
221 some = true;
222 result.insert(r);
223 } else {
224 every = false;
225 }
226 }
227
228 if !some {
229 RuntimeCondition::Boolean(false)
230 } else if every {
231 RuntimeCondition::Boolean(true)
232 } else {
233 RuntimeCondition::Spec(RuntimeSpec::new(result))
234 }
235 }
236 }
237}
238
239pub fn merge_runtime_condition_non_false(
241 a: &RuntimeCondition,
242 b: &RuntimeCondition,
243 runtime: Option<&RuntimeSpec>,
244) -> RuntimeCondition {
245 let merged = match (a, b) {
246 (RuntimeCondition::Boolean(true), _) => return RuntimeCondition::Boolean(true),
247 (RuntimeCondition::Boolean(false), _) => unreachable!(),
248 (_, RuntimeCondition::Boolean(false)) => unreachable!(),
249 (RuntimeCondition::Spec(_), RuntimeCondition::Boolean(true)) => {
250 return RuntimeCondition::Boolean(true);
251 }
252 (RuntimeCondition::Spec(a), RuntimeCondition::Spec(b)) => merge_runtime(a, b),
253 };
254 if runtime.map(|spec| spec.len()).unwrap_or_default() == merged.len() {
255 return RuntimeCondition::Boolean(true);
256 }
257 RuntimeCondition::Spec(merged)
258}
259
260pub fn merge_runtime_condition(
261 a: &RuntimeCondition,
262 b: &RuntimeCondition,
263 runtime: Option<&RuntimeSpec>,
264) -> RuntimeCondition {
265 let merged = match (a, b) {
266 (RuntimeCondition::Boolean(false), _) => return b.clone(),
267 (_, RuntimeCondition::Boolean(false)) => return a.clone(),
268 (_, RuntimeCondition::Boolean(true)) | (RuntimeCondition::Boolean(true), _) => {
269 return RuntimeCondition::Boolean(true);
270 }
271 (RuntimeCondition::Spec(a), RuntimeCondition::Spec(b)) => merge_runtime(a, b),
272 };
273 if runtime.map(|spec| spec.len()).unwrap_or_default() == merged.len() {
274 return RuntimeCondition::Boolean(true);
275 }
276 RuntimeCondition::Spec(merged)
277}
278
279pub fn subtract_runtime_condition(
280 a: &RuntimeCondition,
281 b: &RuntimeCondition,
282 runtime: Option<&RuntimeSpec>,
283) -> RuntimeCondition {
284 let merged = match (a, b) {
285 (_, RuntimeCondition::Boolean(true)) => return RuntimeCondition::Boolean(false),
286 (_, RuntimeCondition::Boolean(false)) => return a.clone(),
287 (RuntimeCondition::Boolean(false), _) => return RuntimeCondition::Boolean(false),
288 (RuntimeCondition::Spec(a), RuntimeCondition::Spec(b)) => a.difference(b).copied().collect(),
289 (RuntimeCondition::Boolean(true), RuntimeCondition::Spec(b)) => {
290 if let Some(a) = runtime {
291 a.difference(b).copied().collect()
292 } else {
293 UstrSet::default()
294 }
295 }
296 };
297 if merged.is_empty() {
298 return RuntimeCondition::Boolean(false);
299 }
300 RuntimeCondition::Spec(merged.into())
301}
302
303pub fn get_runtime_key(runtime: &RuntimeSpec) -> &RuntimeKey {
304 &runtime.key
305}
306
307pub fn compare_runtime(a: &RuntimeSpec, b: &RuntimeSpec) -> Ordering {
308 a.key.cmp(&b.key)
309}
310
311#[derive(Default, Clone, Debug, PartialEq, Eq)]
312pub struct RuntimeSpecMap<T> {
313 pub mode: RuntimeMode,
314 pub map: RuntimeKeyMap<T>,
315
316 pub single_runtime: Option<RuntimeSpec>,
317 pub single_value: Option<T>,
318}
319
320impl<T> RuntimeSpecMap<T> {
321 pub fn new() -> Self {
322 Self {
323 mode: RuntimeMode::Empty,
324 map: Default::default(),
325 single_runtime: None,
326 single_value: None,
327 }
328 }
329
330 pub fn size(&self) -> usize {
331 let mode = self.mode as usize;
332
333 if mode <= 1 { mode } else { self.map.len() }
334 }
335
336 pub fn get(&self, runtime: &RuntimeSpec) -> Option<&T> {
337 match self.mode {
338 RuntimeMode::Empty => None,
339 RuntimeMode::SingleEntry => {
340 if let Some(single_runtime) = self.single_runtime.as_ref()
341 && is_runtime_equal(single_runtime, runtime)
342 {
343 self.single_value.as_ref()
344 } else {
345 None
346 }
347 }
348 RuntimeMode::Map => self.map.get(get_runtime_key(runtime)),
349 }
350 }
351
352 pub fn get_mut(&mut self, runtime: &RuntimeSpec) -> Option<&mut T> {
353 match self.mode {
354 RuntimeMode::Empty => None,
355 RuntimeMode::SingleEntry => {
356 if let Some(single_runtime) = self.single_runtime.as_ref()
357 && is_runtime_equal(single_runtime, runtime)
358 {
359 self.single_value.as_mut()
360 } else {
361 None
362 }
363 }
364 RuntimeMode::Map => self.map.get_mut(get_runtime_key(runtime)),
365 }
366 }
367
368 pub fn set(&mut self, runtime: RuntimeSpec, value: T) {
369 match self.mode {
370 RuntimeMode::Empty => {
371 self.mode = RuntimeMode::SingleEntry;
372 self.single_runtime = Some(runtime);
373 self.single_value = Some(value);
374 }
375 RuntimeMode::SingleEntry => {
376 if let Some(single_runtime) = self.single_runtime.as_ref()
377 && is_runtime_equal(single_runtime, &runtime)
378 {
379 self.single_value = Some(value);
380 } else {
381 self.mode = RuntimeMode::Map;
382
383 let single_runtime = self
384 .single_runtime
385 .take()
386 .expect("Expected single runtime exists");
387 let single_value = self
388 .single_value
389 .take()
390 .expect("Expected single value exists");
391
392 self
393 .map
394 .insert(get_runtime_key(&single_runtime).clone(), single_value);
395 self.map.insert(get_runtime_key(&runtime).clone(), value);
396 }
397 }
398 RuntimeMode::Map => {
399 self.map.insert(get_runtime_key(&runtime).clone(), value);
400 }
401 }
402 }
403
404 pub fn values(&self) -> RuntimeSpecMapValues<'_, T> {
405 match self.mode {
406 RuntimeMode::Empty => RuntimeSpecMapValues::Empty,
407 RuntimeMode::SingleEntry => RuntimeSpecMapValues::SingleEntry(self.single_value.iter()),
408 RuntimeMode::Map => RuntimeSpecMapValues::Map(self.map.values()),
409 }
410 }
411}
412
413pub enum RuntimeSpecMapValues<'a, T> {
414 Empty,
415 SingleEntry(std::option::Iter<'a, T>),
416 Map(hash_map::Values<'a, RuntimeKey, T>),
417}
418
419impl<'a, T> Iterator for RuntimeSpecMapValues<'a, T> {
420 type Item = &'a T;
421
422 fn next(&mut self) -> Option<Self::Item> {
423 match self {
424 RuntimeSpecMapValues::Empty => None,
425 RuntimeSpecMapValues::SingleEntry(i) => i.next(),
426 RuntimeSpecMapValues::Map(i) => i.next(),
427 }
428 }
429
430 fn size_hint(&self) -> (usize, Option<usize>) {
431 match self {
432 RuntimeSpecMapValues::Empty => (0, Some(0)),
433 RuntimeSpecMapValues::SingleEntry(i) => i.size_hint(),
434 RuntimeSpecMapValues::Map(i) => i.size_hint(),
435 }
436 }
437}
438
439#[derive(Default, Debug)]
440pub struct RuntimeSpecSet {
441 map: RuntimeKeyMap<RuntimeSpec>,
442}
443
444impl RuntimeSpecSet {
445 pub fn get(&self, runtime: &RuntimeSpec) -> Option<&RuntimeSpec> {
446 self.map.get(get_runtime_key(runtime))
447 }
448
449 pub fn set(&mut self, runtime: RuntimeSpec) {
450 self.map.insert(get_runtime_key(&runtime).clone(), runtime);
451 }
452
453 pub fn contains(&self, runtime: &RuntimeSpec) -> bool {
454 self.map.contains_key(get_runtime_key(runtime))
455 }
456
457 pub fn values(&self) -> hash_map::Values<'_, RuntimeKey, RuntimeSpec> {
458 self.map.values()
459 }
460
461 pub fn into_values(self) -> hash_map::IntoValues<RuntimeKey, RuntimeSpec> {
462 self.map.into_values()
463 }
464
465 pub fn len(&self) -> usize {
466 self.map.len()
467 }
468
469 pub fn is_empty(&self) -> bool {
470 self.len() == 0
471 }
472}