1use super::{
2 assignments::DeclarationAssignmentProbe,
3 command_options::PathWordFact,
4 commands::CommandFact,
5 redirects::{ComparableNameUse, RedirectFact},
6 substitutions::SubstitutionFact,
7};
8use rustc_hash::FxHashMap;
9use shuck_ast::{Command, IdRange, ListArena, Name, Redirect, Span, Stmt};
10use shuck_semantic::SemanticModel;
11use smallvec::SmallVec;
12
13pub use shuck_semantic::CommandId;
14
15#[derive(Debug, Clone, Copy)]
16pub(crate) struct CommandVisit<'a> {
17 pub(crate) stmt: &'a Stmt,
18 pub(crate) command: &'a Command,
19 pub(crate) redirects: &'a [Redirect],
20}
21
22impl<'a> CommandVisit<'a> {
23 pub(crate) fn new(stmt: &'a Stmt) -> Self {
24 Self {
25 stmt,
26 command: &stmt.command,
27 redirects: &stmt.redirects,
28 }
29 }
30}
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
33pub struct FactSpan {
34 start: usize,
35 end: usize,
36}
37
38impl FactSpan {
39 pub fn new(span: Span) -> Self {
40 Self {
41 start: span.start.offset,
42 end: span.end.offset,
43 }
44 }
45}
46
47impl From<Span> for FactSpan {
48 fn from(span: Span) -> Self {
49 Self::new(span)
50 }
51}
52
53#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
54pub struct WordNodeId(u32);
55
56impl WordNodeId {
57 pub(crate) fn new(index: usize) -> Self {
58 Self(fact_id_index_to_u32(index, "word node id"))
59 }
60
61 pub(crate) fn index(self) -> usize {
62 self.0 as usize
63 }
64}
65
66#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
67pub struct WordOccurrenceId(u32);
68
69impl WordOccurrenceId {
70 pub(crate) fn new(index: usize) -> Self {
71 Self(fact_id_index_to_u32(index, "word occurrence id"))
72 }
73
74 pub(crate) fn index(self) -> usize {
75 self.0 as usize
76 }
77}
78
79#[inline]
80fn fact_id_index_to_u32(index: usize, kind: &'static str) -> u32 {
81 if index > u32::MAX as usize {
82 fact_id_index_overflow(kind);
83 }
84 index as u32
85}
86
87#[cold]
88#[inline(never)]
89fn fact_id_index_overflow(kind: &'static str) -> ! {
90 panic!("{kind} must fit in u32");
91}
92
93#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
94pub(crate) enum CommandLookupKind {
95 Simple,
96 Builtin(BuiltinLookupKind),
97 Decl,
98 Binary,
99 Compound(CompoundLookupKind),
100 Function,
101 AnonymousFunction,
102}
103
104#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
105pub(crate) enum BuiltinLookupKind {
106 Break,
107 Continue,
108 Return,
109 Exit,
110}
111
112#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
113pub(crate) enum CompoundLookupKind {
114 If,
115 For,
116 Repeat,
117 Foreach,
118 ArithmeticFor,
119 While,
120 Until,
121 Case,
122 Select,
123 Subshell,
124 BraceGroup,
125 Arithmetic,
126 Time,
127 Conditional,
128 Coproc,
129 Always,
130}
131
132#[derive(Debug, Clone, Copy)]
133pub(crate) struct CommandLookupEntry {
134 pub(crate) kind: CommandLookupKind,
135 pub(crate) id: CommandId,
136}
137
138pub(crate) type CommandLookupIndex = FxHashMap<FactSpan, SmallVec<[CommandLookupEntry; 1]>>;
139
140#[derive(Debug, Clone, Default)]
141pub(crate) struct DenseCommandIdSet {
142 words: Vec<u64>,
143}
144
145impl DenseCommandIdSet {
146 const BITS: usize = u64::BITS as usize;
147
148 pub(crate) fn with_capacity(command_count: usize) -> Self {
149 Self {
150 words: vec![0; command_count.div_ceil(Self::BITS)],
151 }
152 }
153
154 pub(crate) fn insert(&mut self, id: CommandId) {
155 let index = id.index();
156 let word = index / Self::BITS;
157 let bit = index % Self::BITS;
158 if word >= self.words.len() {
159 self.words.resize(word + 1, 0);
160 }
161 self.words[word] |= 1u64 << bit;
162 }
163
164 pub(crate) fn contains(&self, id: CommandId) -> bool {
165 let index = id.index();
166 let word = index / Self::BITS;
167 let bit = index % Self::BITS;
168 self.words
169 .get(word)
170 .is_some_and(|w| (w & (1u64 << bit)) != 0)
171 }
172}
173
174#[derive(Debug, Clone)]
175pub(crate) struct FactStore<'a> {
176 pub(crate) redirect_facts: ListArena<RedirectFact<'a>>,
177 pub(crate) substitution_facts: ListArena<SubstitutionFact>,
178 pub(crate) scope_read_source_words: ListArena<PathWordFact<'a>>,
179 pub(crate) scope_name_read_uses: ListArena<ComparableNameUse>,
180 pub(crate) scope_heredoc_name_read_uses: ListArena<ComparableNameUse>,
181 pub(crate) scope_name_write_uses: ListArena<ComparableNameUse>,
182 pub(crate) declaration_assignment_probes: ListArena<DeclarationAssignmentProbe>,
183 pub(crate) word_occurrence_ids: ListArena<WordOccurrenceId>,
184 pub(crate) word_occurrence_ids_by_command: Vec<IdRange<WordOccurrenceId>>,
185 pub(crate) word_spans: ListArena<Span>,
186}
187
188#[derive(Debug, Clone)]
189pub(crate) struct CommandChildIndex {
190 ids: ListArena<CommandId>,
191 by_parent: Vec<IdRange<CommandId>>,
192}
193
194impl CommandChildIndex {
195 pub(crate) fn from_semantic_syntax_backed_children(
196 semantic: &SemanticModel,
197 command_fact_indices_by_id: &[Option<usize>],
198 ) -> Self {
199 let total_children = semantic
200 .commands()
201 .iter()
202 .copied()
203 .map(|parent| {
204 semantic
205 .syntax_backed_command_children(parent)
206 .iter()
207 .copied()
208 .filter(|child| command_fact_exists(command_fact_indices_by_id, *child))
209 .count()
210 })
211 .sum();
212 let mut ids = ListArena::with_capacity(total_children);
213 let mut by_parent = Vec::with_capacity(semantic.command_count());
214 by_parent.resize_with(semantic.command_count(), IdRange::empty);
215
216 for parent in semantic.commands().iter().copied() {
217 by_parent[parent.index()] = ids.push_many(
218 semantic
219 .syntax_backed_command_children(parent)
220 .iter()
221 .copied()
222 .filter(|child| command_fact_exists(command_fact_indices_by_id, *child)),
223 );
224 }
225
226 Self { ids, by_parent }
227 }
228
229 pub(crate) fn child_ids(&self, id: CommandId) -> &[CommandId] {
230 self.by_parent
231 .get(id.index())
232 .copied()
233 .map_or(&[], |range| self.ids.get(range))
234 }
235}
236
237fn command_fact_exists(command_fact_indices_by_id: &[Option<usize>], id: CommandId) -> bool {
238 command_fact_indices_by_id
239 .get(id.index())
240 .is_some_and(Option::is_some)
241}
242
243impl<'a> FactStore<'a> {
244 pub(crate) fn empty() -> Self {
245 Self {
246 redirect_facts: ListArena::new(),
247 substitution_facts: ListArena::new(),
248 scope_read_source_words: ListArena::new(),
249 scope_name_read_uses: ListArena::new(),
250 scope_heredoc_name_read_uses: ListArena::new(),
251 scope_name_write_uses: ListArena::new(),
252 declaration_assignment_probes: ListArena::new(),
253 word_occurrence_ids: ListArena::new(),
254 word_occurrence_ids_by_command: Vec::new(),
255 word_spans: ListArena::new(),
256 }
257 }
258
259 pub(crate) fn redirect_facts(&self, range: IdRange<RedirectFact<'a>>) -> &[RedirectFact<'a>] {
260 self.redirect_facts.get(range)
261 }
262
263 pub(crate) fn substitution_facts(
264 &self,
265 range: IdRange<SubstitutionFact>,
266 ) -> &[SubstitutionFact] {
267 self.substitution_facts.get(range)
268 }
269
270 pub(crate) fn scope_read_source_words(
271 &self,
272 range: IdRange<PathWordFact<'a>>,
273 ) -> &[PathWordFact<'a>] {
274 self.scope_read_source_words.get(range)
275 }
276
277 pub(crate) fn scope_name_read_uses(
278 &self,
279 range: IdRange<ComparableNameUse>,
280 ) -> &[ComparableNameUse] {
281 self.scope_name_read_uses.get(range)
282 }
283
284 pub(crate) fn scope_heredoc_name_read_uses(
285 &self,
286 range: IdRange<ComparableNameUse>,
287 ) -> &[ComparableNameUse] {
288 self.scope_heredoc_name_read_uses.get(range)
289 }
290
291 pub(crate) fn scope_name_write_uses(
292 &self,
293 range: IdRange<ComparableNameUse>,
294 ) -> &[ComparableNameUse] {
295 self.scope_name_write_uses.get(range)
296 }
297
298 pub(crate) fn declaration_assignment_probes(
299 &self,
300 range: IdRange<DeclarationAssignmentProbe>,
301 ) -> &[DeclarationAssignmentProbe] {
302 self.declaration_assignment_probes.get(range)
303 }
304
305 pub(crate) fn word_occurrence_ids_for_command(&self, id: CommandId) -> &[WordOccurrenceId] {
306 self.word_occurrence_ids_by_command
307 .get(id.index())
308 .copied()
309 .map_or(&[], |range| self.word_occurrence_ids.get(range))
310 }
311
312 pub(crate) fn word_spans(&self, range: IdRange<Span>) -> &[Span] {
313 self.word_spans.get(range)
314 }
315}
316
317#[derive(Clone, Copy)]
318pub struct CommandFactRef<'facts, 'a> {
319 pub(crate) fact: &'facts CommandFact<'a>,
320 pub(crate) store: &'facts FactStore<'a>,
321}
322
323impl<'facts, 'a> CommandFactRef<'facts, 'a> {
324 pub(crate) fn new(fact: &'facts CommandFact<'a>, store: &'facts FactStore<'a>) -> Self {
325 Self { fact, store }
326 }
327}
328
329impl<'facts, 'a> std::fmt::Debug for CommandFactRef<'facts, 'a> {
330 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
331 self.fact.fmt(formatter)
332 }
333}
334
335impl<'facts, 'a> std::ops::Deref for CommandFactRef<'facts, 'a> {
336 type Target = CommandFact<'a>;
337
338 fn deref(&self) -> &Self::Target {
339 self.fact
340 }
341}
342
343#[derive(Clone, Copy)]
344pub struct CommandFacts<'facts, 'a> {
345 pub(crate) commands: &'facts [CommandFact<'a>],
346 pub(crate) store: &'facts FactStore<'a>,
347 pub(crate) indices_by_id: &'facts [Option<usize>],
348}
349
350impl<'facts, 'a> CommandFacts<'facts, 'a> {
351 pub(crate) fn new(
352 commands: &'facts [CommandFact<'a>],
353 store: &'facts FactStore<'a>,
354 indices_by_id: &'facts [Option<usize>],
355 ) -> Self {
356 Self {
357 commands,
358 store,
359 indices_by_id,
360 }
361 }
362
363 pub fn len(self) -> usize {
364 self.commands.len()
365 }
366
367 pub fn is_empty(self) -> bool {
368 self.commands.is_empty()
369 }
370
371 pub fn iter(self) -> CommandFactIter<'facts, 'a> {
372 CommandFactIter {
373 inner: self.commands.iter(),
374 store: self.store,
375 }
376 }
377
378 #[cfg(test)]
379 pub(crate) fn raw(self) -> &'facts [CommandFact<'a>] {
380 self.commands
381 }
382
383 pub fn get(self, index: usize) -> Option<CommandFactRef<'facts, 'a>> {
384 self.commands
385 .get(index)
386 .map(|fact| CommandFactRef::new(fact, self.store))
387 }
388
389 pub fn find(self, id: CommandId) -> Option<CommandFactRef<'facts, 'a>> {
390 self.indices_by_id
391 .get(id.index())
392 .copied()
393 .flatten()
394 .and_then(|index| self.commands.get(index))
395 .map(|fact| CommandFactRef::new(fact, self.store))
396 }
397
398 pub(crate) fn index_of(self, id: CommandId) -> Option<usize> {
399 self.indices_by_id.get(id.index()).copied().flatten()
400 }
401
402 pub(crate) fn iter_from(self, start: usize) -> CommandFactIter<'facts, 'a> {
403 let slice = self.commands.get(start..).unwrap_or(&[]);
404 CommandFactIter {
405 inner: slice.iter(),
406 store: self.store,
407 }
408 }
409
410 pub(crate) fn contained_in(
417 self,
418 outer: Span,
419 ) -> impl Iterator<Item = CommandFactRef<'facts, 'a>> {
420 let start_offset = outer.start.offset;
421 let end_offset = outer.end.offset;
422 let start = self
423 .commands
424 .partition_point(|fact| fact.span().start.offset < start_offset);
425 let store = self.store;
426 self.commands[start..]
427 .iter()
428 .take_while(move |fact| fact.span().start.offset <= end_offset)
429 .filter(move |fact| fact.span().end.offset <= end_offset)
430 .map(move |fact| CommandFactRef::new(fact, store))
431 }
432
433 pub fn first(self) -> Option<CommandFactRef<'facts, 'a>> {
434 self.get(0)
435 }
436
437 pub fn last(self) -> Option<CommandFactRef<'facts, 'a>> {
438 self.commands
439 .last()
440 .map(|fact| CommandFactRef::new(fact, self.store))
441 }
442}
443
444impl<'facts, 'a> IntoIterator for CommandFacts<'facts, 'a> {
445 type Item = CommandFactRef<'facts, 'a>;
446 type IntoIter = CommandFactIter<'facts, 'a>;
447
448 fn into_iter(self) -> Self::IntoIter {
449 self.iter()
450 }
451}
452
453impl<'facts, 'a> IntoIterator for &CommandFacts<'facts, 'a> {
454 type Item = CommandFactRef<'facts, 'a>;
455 type IntoIter = CommandFactIter<'facts, 'a>;
456
457 fn into_iter(self) -> Self::IntoIter {
458 (*self).iter()
459 }
460}
461
462#[derive(Clone)]
463pub struct CommandFactIter<'facts, 'a> {
464 inner: std::slice::Iter<'facts, CommandFact<'a>>,
465 store: &'facts FactStore<'a>,
466}
467
468impl<'facts, 'a> Iterator for CommandFactIter<'facts, 'a> {
469 type Item = CommandFactRef<'facts, 'a>;
470
471 fn next(&mut self) -> Option<Self::Item> {
472 self.inner
473 .next()
474 .map(|fact| CommandFactRef::new(fact, self.store))
475 }
476
477 fn size_hint(&self) -> (usize, Option<usize>) {
478 self.inner.size_hint()
479 }
480}
481
482impl<'facts, 'a> DoubleEndedIterator for CommandFactIter<'facts, 'a> {
483 fn next_back(&mut self) -> Option<Self::Item> {
484 self.inner
485 .next_back()
486 .map(|fact| CommandFactRef::new(fact, self.store))
487 }
488}
489
490impl<'facts, 'a> ExactSizeIterator for CommandFactIter<'facts, 'a> {}
491
492#[derive(Debug, Clone, Copy, PartialEq, Eq)]
493pub enum SudoFamilyInvoker {
494 Sudo,
495 Doas,
496 Run0,
497}
498
499#[derive(Debug, Clone, PartialEq, Eq)]
500pub struct NamedSpan {
501 pub name: Name,
502 pub span: Span,
503}
504
505#[derive(Debug, Clone, PartialEq, Eq)]
506pub struct BacktickEscapedParameter {
507 pub name: Option<Name>,
508 pub diagnostic_span: Span,
509 pub reference_span: Span,
510 pub standalone_command_name: bool,
511}