1use crate::SourceLocation;
4use perl_semantic_facts::{
5 AnchorId, Confidence, ExportSet, ExportTag, FileId, ImportKind, ImportSpec, ImportSymbols,
6 Provenance, ScopeId, VisibleSymbol, VisibleSymbolContext, VisibleSymbolSource,
7};
8use std::collections::BTreeMap;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
12#[non_exhaustive]
13pub struct HirId {
14 index: u32,
15}
16
17impl HirId {
18 #[inline]
20 pub const fn from_index(index: u32) -> Self {
21 Self { index }
22 }
23
24 #[inline]
26 pub const fn index(self) -> u32 {
27 self.index
28 }
29}
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
33#[non_exhaustive]
34pub struct HirScopeId {
35 index: u32,
36}
37
38impl HirScopeId {
39 #[inline]
41 pub const fn from_index(index: u32) -> Self {
42 Self { index }
43 }
44
45 #[inline]
47 pub const fn index(self) -> u32 {
48 self.index
49 }
50}
51
52#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
54#[non_exhaustive]
55pub struct HirBindingId {
56 index: u32,
57}
58
59impl HirBindingId {
60 #[inline]
62 pub const fn from_index(index: u32) -> Self {
63 Self { index }
64 }
65
66 #[inline]
68 pub const fn index(self) -> u32 {
69 self.index
70 }
71}
72
73#[derive(Debug, Clone, PartialEq, Eq)]
75#[non_exhaustive]
76pub struct AstAnchor {
77 pub node_kind: &'static str,
79 pub range: SourceLocation,
81 pub name_range: Option<SourceLocation>,
83}
84
85#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
87#[non_exhaustive]
88pub enum RecoveryConfidence {
89 Parsed,
91 Recovered,
93 Partial,
95 Unknown,
97}
98
99#[derive(Debug, Clone, PartialEq, Eq, Default)]
101#[non_exhaustive]
102pub struct HirFile {
103 pub items: Vec<HirItem>,
105 pub scope_graph: ScopeGraph,
107 pub stash_graph: StashGraph,
109 pub compile_environment: CompileEnvironment,
111}
112
113impl HirFile {
114 #[inline]
116 pub fn is_empty(&self) -> bool {
117 self.items.is_empty()
118 }
119
120 #[must_use]
126 pub fn compile_effects(&self) -> Vec<CompileEffect> {
127 self.compile_effects_with_source_hash(None)
128 }
129
130 #[must_use]
136 pub fn compile_effects_with_source_hash(
137 &self,
138 source_hash: Option<String>,
139 ) -> Vec<CompileEffect> {
140 compile_effects_from_file(self, source_hash)
141 }
142
143 #[must_use]
148 pub fn framework_facts(&self) -> FrameworkFactGraph {
149 FrameworkAdapterRegistry::default().project_file(self)
150 }
151}
152
153#[derive(Debug, Clone, PartialEq, Eq)]
155#[non_exhaustive]
156pub struct HirItem {
157 pub id: HirId,
159 pub kind: HirKind,
161 pub range: SourceLocation,
163 pub anchor: AstAnchor,
165 pub recovery_confidence: RecoveryConfidence,
167 pub package_context: Option<String>,
169 pub scope_context: Option<HirScopeId>,
171}
172
173pub const COMPILE_EFFECT_MODEL_VERSION: u32 = 1;
175
176#[derive(Debug, Clone, PartialEq, Eq)]
182#[non_exhaustive]
183pub struct CompileEffect {
184 pub ordinal: u32,
186 pub kind: CompileEffectKind,
188 pub source_kind: CompileEffectSourceKind,
190 pub fact_kind: CompileEffectFactKind,
192 pub fact_name: Option<String>,
194 pub range: SourceLocation,
196 pub source_item: Option<HirId>,
198 pub scope_id: Option<HirScopeId>,
200 pub package_context: Option<String>,
202 pub fact_anchor_id: Option<AnchorId>,
204 pub dynamic_reason: Option<String>,
206 pub source_hash: Option<String>,
208 pub model_version: u32,
210 pub provenance: CompileProvenance,
212 pub confidence: CompileConfidence,
214}
215
216#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
218#[non_exhaustive]
219pub enum CompileEffectKind {
220 DeclarePackage,
222 DeclareSub,
224 DeclareMethod,
226 DeclareBinding,
228 SetPragmaState,
230 AddIncludePath,
232 RemoveIncludePath,
234 RequestModule,
236 ImportSymbols,
238 AssignInheritance,
240 AssignGlobAlias,
242 DefineConstant,
244 RegisterPrototype,
246 EmitDynamicBoundary,
248}
249
250#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
252#[non_exhaustive]
253pub enum CompileEffectSourceKind {
254 PackageDecl,
256 SubDecl,
258 MethodDecl,
260 VariableDecl,
262 UseDirective,
264 NoDirective,
266 RequireDirective,
268 PhaseBlock,
270 SymbolicReferenceDeref,
272 Assignment,
274 TypeglobAssignment,
276 ScopeGraph,
278 StashGraph,
280 CompileEnvironment,
282}
283
284#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
286#[non_exhaustive]
287pub enum CompileEffectFactKind {
288 Package,
290 Sub,
292 Method,
294 Binding,
296 PragmaState,
298 IncludeRoot,
300 ModuleRequest,
302 ImportSpec,
304 InheritanceEdge,
306 GlobSlot,
308 Constant,
310 Prototype,
312 DynamicBoundary,
314}
315
316#[derive(Debug)]
317struct CompileEffectEntry {
318 source_order: u32,
319 effect: CompileEffect,
320}
321
322fn compile_effects_from_file(file: &HirFile, source_hash: Option<String>) -> Vec<CompileEffect> {
323 let mut entries = Vec::new();
324 let mut next_order = 0;
325
326 for item in &file.items {
327 push_item_effects(item, &source_hash, &mut entries, &mut next_order);
328 }
329 for binding in &file.scope_graph.bindings {
330 push_compile_effect(
331 &mut entries,
332 &mut next_order,
333 CompileEffectSeed {
334 kind: CompileEffectKind::DeclareBinding,
335 source_kind: CompileEffectSourceKind::ScopeGraph,
336 fact_kind: CompileEffectFactKind::Binding,
337 fact_name: Some(format!("{}{}", binding.sigil, binding.name)),
338 range: binding.range,
339 source_item: binding.declaration_item,
340 scope_id: Some(binding.scope_id),
341 package_context: binding.package_context.clone(),
342 fact_anchor_id: Some(AnchorId(binding.range.start as u64)),
343 dynamic_reason: None,
344 source_hash: source_hash.clone(),
345 provenance: CompileProvenance::ExactAst,
346 confidence: CompileConfidence::High,
347 },
348 );
349 }
350 for fact in &file.compile_environment.pragma_state_facts {
351 push_compile_effect(
352 &mut entries,
353 &mut next_order,
354 CompileEffectSeed {
355 kind: CompileEffectKind::SetPragmaState,
356 source_kind: CompileEffectSourceKind::CompileEnvironment,
357 fact_kind: CompileEffectFactKind::PragmaState,
358 fact_name: Some("strict/warnings/feature".to_string()),
359 range: fact.range,
360 source_item: fact.directive_item,
361 scope_id: fact.scope_id,
362 package_context: fact.package_context.clone(),
363 fact_anchor_id: Some(fact.anchor_id),
364 dynamic_reason: None,
365 source_hash: source_hash.clone(),
366 provenance: fact.provenance,
367 confidence: fact.confidence,
368 },
369 );
370 }
371 for root in &file.compile_environment.inc_roots {
372 let kind = match root.action {
373 IncRootAction::Add => CompileEffectKind::AddIncludePath,
374 IncRootAction::Remove => CompileEffectKind::RemoveIncludePath,
375 };
376 push_compile_effect(
377 &mut entries,
378 &mut next_order,
379 CompileEffectSeed {
380 kind,
381 source_kind: match root.action {
382 IncRootAction::Add => CompileEffectSourceKind::UseDirective,
383 IncRootAction::Remove => CompileEffectSourceKind::NoDirective,
384 },
385 fact_kind: CompileEffectFactKind::IncludeRoot,
386 fact_name: Some(root.path.clone()),
387 range: root.range,
388 source_item: root.directive_item,
389 scope_id: root.scope_id,
390 package_context: root.package_context.clone(),
391 fact_anchor_id: Some(AnchorId(root.range.start as u64)),
392 dynamic_reason: None,
393 source_hash: source_hash.clone(),
394 provenance: root.provenance,
395 confidence: root.confidence,
396 },
397 );
398 }
399 for request in &file.compile_environment.module_requests {
400 push_compile_effect(
401 &mut entries,
402 &mut next_order,
403 CompileEffectSeed {
404 kind: CompileEffectKind::RequestModule,
405 source_kind: module_request_source_kind(request.kind),
406 fact_kind: CompileEffectFactKind::ModuleRequest,
407 fact_name: request.target.clone().or_else(|| Some("<dynamic>".to_string())),
408 range: request.range,
409 source_item: request.directive_item,
410 scope_id: request.scope_id,
411 package_context: request.package_context.clone(),
412 fact_anchor_id: Some(AnchorId(request.range.start as u64)),
413 dynamic_reason: None,
414 source_hash: source_hash.clone(),
415 provenance: request.provenance,
416 confidence: request.confidence,
417 },
418 );
419 }
420 for spec in file.compile_environment.import_specs(FileId(0)) {
421 push_compile_effect(
422 &mut entries,
423 &mut next_order,
424 CompileEffectSeed {
425 kind: CompileEffectKind::ImportSymbols,
426 source_kind: import_spec_source_kind(&spec),
427 fact_kind: CompileEffectFactKind::ImportSpec,
428 fact_name: Some(spec.module.clone()),
429 range: SourceLocation::new(
430 spec.span_start_byte.unwrap_or_default() as usize,
431 spec.span_start_byte.unwrap_or_default() as usize,
432 ),
433 source_item: None,
434 scope_id: spec.scope_id.map(|scope| HirScopeId::from_index(scope.0 as u32)),
435 package_context: None,
436 fact_anchor_id: spec.anchor_id,
437 dynamic_reason: None,
438 source_hash: source_hash.clone(),
439 provenance: fact_provenance_to_compile(spec.provenance),
440 confidence: fact_confidence_to_compile(spec.confidence),
441 },
442 );
443 }
444 for edge in &file.stash_graph.inheritance_edges {
445 push_compile_effect(
446 &mut entries,
447 &mut next_order,
448 CompileEffectSeed {
449 kind: CompileEffectKind::AssignInheritance,
450 source_kind: CompileEffectSourceKind::StashGraph,
451 fact_kind: CompileEffectFactKind::InheritanceEdge,
452 fact_name: Some(format!("{}->{}", edge.from_package, edge.to_package)),
453 range: edge.range,
454 source_item: edge.declaration_item,
455 scope_id: None,
456 package_context: Some(edge.from_package.clone()),
457 fact_anchor_id: Some(AnchorId(edge.range.start as u64)),
458 dynamic_reason: None,
459 source_hash: source_hash.clone(),
460 provenance: stash_provenance_to_compile(edge.provenance),
461 confidence: stash_confidence_to_compile(edge.confidence),
462 },
463 );
464 }
465 for package in &file.stash_graph.packages {
466 for slot in &package.slots {
467 push_slot_effects(package, slot, &source_hash, &mut entries, &mut next_order);
468 }
469 }
470 for boundary in &file.compile_environment.dynamic_boundaries {
471 push_compile_effect(
472 &mut entries,
473 &mut next_order,
474 CompileEffectSeed {
475 kind: CompileEffectKind::EmitDynamicBoundary,
476 source_kind: compile_boundary_source_kind(boundary.kind),
477 fact_kind: CompileEffectFactKind::DynamicBoundary,
478 fact_name: Some(format!("{:?}", boundary.kind)),
479 range: boundary.range,
480 source_item: boundary.boundary_item,
481 scope_id: boundary.scope_id,
482 package_context: boundary.package_context.clone(),
483 fact_anchor_id: Some(AnchorId(boundary.range.start as u64)),
484 dynamic_reason: Some(boundary.reason.clone()),
485 source_hash: source_hash.clone(),
486 provenance: boundary.provenance,
487 confidence: boundary.confidence,
488 },
489 );
490 }
491 for boundary in &file.stash_graph.dynamic_boundaries {
492 push_compile_effect(
493 &mut entries,
494 &mut next_order,
495 CompileEffectSeed {
496 kind: CompileEffectKind::EmitDynamicBoundary,
497 source_kind: CompileEffectSourceKind::StashGraph,
498 fact_kind: CompileEffectFactKind::DynamicBoundary,
499 fact_name: boundary.symbol.clone(),
500 range: boundary.range,
501 source_item: boundary.boundary_item,
502 scope_id: None,
503 package_context: boundary.package.clone(),
504 fact_anchor_id: Some(AnchorId(boundary.range.start as u64)),
505 dynamic_reason: Some(boundary.reason.clone()),
506 source_hash: source_hash.clone(),
507 provenance: stash_provenance_to_compile(boundary.provenance),
508 confidence: stash_confidence_to_compile(boundary.confidence),
509 },
510 );
511 }
512
513 entries.sort_by_key(|entry| (entry.effect.range.start, entry.source_order));
514 entries
515 .into_iter()
516 .enumerate()
517 .map(|(ordinal, mut entry)| {
518 entry.effect.ordinal = ordinal as u32;
519 entry.effect
520 })
521 .collect()
522}
523
524fn push_item_effects(
525 item: &HirItem,
526 source_hash: &Option<String>,
527 entries: &mut Vec<CompileEffectEntry>,
528 next_order: &mut u32,
529) {
530 match &item.kind {
531 HirKind::PackageDecl(decl) => {
532 push_compile_effect(
533 entries,
534 next_order,
535 CompileEffectSeed {
536 kind: CompileEffectKind::DeclarePackage,
537 source_kind: CompileEffectSourceKind::PackageDecl,
538 fact_kind: CompileEffectFactKind::Package,
539 fact_name: Some(decl.name.clone()),
540 range: item.range,
541 source_item: Some(item.id),
542 scope_id: item.scope_context,
543 package_context: Some(decl.name.clone()),
544 fact_anchor_id: Some(AnchorId(item.range.start as u64)),
545 dynamic_reason: None,
546 source_hash: source_hash.clone(),
547 provenance: CompileProvenance::ExactAst,
548 confidence: CompileConfidence::High,
549 },
550 );
551 }
552 HirKind::SubDecl(decl) => {
553 let Some(name) = &decl.name else {
554 return;
555 };
556 push_compile_effect(
557 entries,
558 next_order,
559 CompileEffectSeed {
560 kind: CompileEffectKind::DeclareSub,
561 source_kind: CompileEffectSourceKind::SubDecl,
562 fact_kind: CompileEffectFactKind::Sub,
563 fact_name: Some(name.clone()),
564 range: item.range,
565 source_item: Some(item.id),
566 scope_id: item.scope_context,
567 package_context: item.package_context.clone(),
568 fact_anchor_id: Some(AnchorId(item.range.start as u64)),
569 dynamic_reason: None,
570 source_hash: source_hash.clone(),
571 provenance: CompileProvenance::ExactAst,
572 confidence: CompileConfidence::High,
573 },
574 );
575 if decl.has_prototype {
576 push_compile_effect(
577 entries,
578 next_order,
579 CompileEffectSeed {
580 kind: CompileEffectKind::RegisterPrototype,
581 source_kind: CompileEffectSourceKind::SubDecl,
582 fact_kind: CompileEffectFactKind::Prototype,
583 fact_name: Some(name.clone()),
584 range: item.range,
585 source_item: Some(item.id),
586 scope_id: item.scope_context,
587 package_context: item.package_context.clone(),
588 fact_anchor_id: Some(AnchorId(item.range.start as u64)),
589 dynamic_reason: None,
590 source_hash: source_hash.clone(),
591 provenance: CompileProvenance::ExactAst,
592 confidence: CompileConfidence::High,
593 },
594 );
595 }
596 }
597 HirKind::MethodDecl(decl) => {
598 push_compile_effect(
599 entries,
600 next_order,
601 CompileEffectSeed {
602 kind: CompileEffectKind::DeclareMethod,
603 source_kind: CompileEffectSourceKind::MethodDecl,
604 fact_kind: CompileEffectFactKind::Method,
605 fact_name: Some(decl.name.clone()),
606 range: item.range,
607 source_item: Some(item.id),
608 scope_id: item.scope_context,
609 package_context: item.package_context.clone(),
610 fact_anchor_id: Some(AnchorId(item.range.start as u64)),
611 dynamic_reason: None,
612 source_hash: source_hash.clone(),
613 provenance: CompileProvenance::ExactAst,
614 confidence: CompileConfidence::High,
615 },
616 );
617 }
618 _ => {}
619 }
620}
621
622fn push_slot_effects(
623 package: &PackageStash,
624 slot: &GlobSlot,
625 source_hash: &Option<String>,
626 entries: &mut Vec<CompileEffectEntry>,
627 next_order: &mut u32,
628) {
629 let (kind, fact_kind) = match slot.source {
630 GlobSlotSource::TypeglobAlias => {
631 (CompileEffectKind::AssignGlobAlias, CompileEffectFactKind::GlobSlot)
632 }
633 GlobSlotSource::ConstantDeclaration => {
634 (CompileEffectKind::DefineConstant, CompileEffectFactKind::Constant)
635 }
636 _ => return,
637 };
638
639 push_compile_effect(
640 entries,
641 next_order,
642 CompileEffectSeed {
643 kind,
644 source_kind: match slot.source {
645 GlobSlotSource::TypeglobAlias => CompileEffectSourceKind::TypeglobAssignment,
646 _ => CompileEffectSourceKind::StashGraph,
647 },
648 fact_kind,
649 fact_name: Some(format!("{}::{}", package.package, slot.name)),
650 range: slot.range,
651 source_item: slot.declaration_item,
652 scope_id: None,
653 package_context: Some(package.package.clone()),
654 fact_anchor_id: Some(AnchorId(slot.range.start as u64)),
655 dynamic_reason: None,
656 source_hash: source_hash.clone(),
657 provenance: stash_provenance_to_compile(slot.provenance),
658 confidence: stash_confidence_to_compile(slot.confidence),
659 },
660 );
661}
662
663#[derive(Debug)]
664struct CompileEffectSeed {
665 kind: CompileEffectKind,
666 source_kind: CompileEffectSourceKind,
667 fact_kind: CompileEffectFactKind,
668 fact_name: Option<String>,
669 range: SourceLocation,
670 source_item: Option<HirId>,
671 scope_id: Option<HirScopeId>,
672 package_context: Option<String>,
673 fact_anchor_id: Option<AnchorId>,
674 dynamic_reason: Option<String>,
675 source_hash: Option<String>,
676 provenance: CompileProvenance,
677 confidence: CompileConfidence,
678}
679
680fn push_compile_effect(
681 entries: &mut Vec<CompileEffectEntry>,
682 next_order: &mut u32,
683 seed: CompileEffectSeed,
684) {
685 let order = *next_order;
686 *next_order += 1;
687 entries.push(CompileEffectEntry {
688 source_order: order,
689 effect: CompileEffect {
690 ordinal: 0,
691 kind: seed.kind,
692 source_kind: seed.source_kind,
693 fact_kind: seed.fact_kind,
694 fact_name: seed.fact_name,
695 range: seed.range,
696 source_item: seed.source_item,
697 scope_id: seed.scope_id,
698 package_context: seed.package_context,
699 fact_anchor_id: seed.fact_anchor_id,
700 dynamic_reason: seed.dynamic_reason,
701 source_hash: seed.source_hash,
702 model_version: COMPILE_EFFECT_MODEL_VERSION,
703 provenance: seed.provenance,
704 confidence: seed.confidence,
705 },
706 });
707}
708
709fn module_request_source_kind(kind: ModuleRequestKind) -> CompileEffectSourceKind {
710 match kind {
711 ModuleRequestKind::Require => CompileEffectSourceKind::RequireDirective,
712 _ => CompileEffectSourceKind::UseDirective,
713 }
714}
715
716fn import_spec_source_kind(spec: &ImportSpec) -> CompileEffectSourceKind {
717 match spec.kind {
718 ImportKind::Require | ImportKind::DynamicRequire => {
719 CompileEffectSourceKind::RequireDirective
720 }
721 _ => CompileEffectSourceKind::UseDirective,
722 }
723}
724
725fn compile_boundary_source_kind(kind: CompileEnvironmentBoundaryKind) -> CompileEffectSourceKind {
726 match kind {
727 CompileEnvironmentBoundaryKind::DynamicRequire => CompileEffectSourceKind::RequireDirective,
728 CompileEnvironmentBoundaryKind::DynamicPragmaArgs
729 | CompileEnvironmentBoundaryKind::DynamicIncRoot => CompileEffectSourceKind::UseDirective,
730 CompileEnvironmentBoundaryKind::PhaseBlockExecution => CompileEffectSourceKind::PhaseBlock,
731 CompileEnvironmentBoundaryKind::SymbolicReferenceDeref => {
732 CompileEffectSourceKind::SymbolicReferenceDeref
733 }
734 }
735}
736
737fn stash_provenance_to_compile(provenance: StashProvenance) -> CompileProvenance {
738 match provenance {
739 StashProvenance::ExactAst => CompileProvenance::ExactAst,
740 StashProvenance::DesugaredAst => CompileProvenance::DesugaredAst,
741 StashProvenance::DynamicBoundary => CompileProvenance::DynamicBoundary,
742 }
743}
744
745fn stash_confidence_to_compile(confidence: StashConfidence) -> CompileConfidence {
746 match confidence {
747 StashConfidence::High => CompileConfidence::High,
748 StashConfidence::Medium => CompileConfidence::Medium,
749 StashConfidence::Low => CompileConfidence::Low,
750 }
751}
752
753fn fact_provenance_to_compile(provenance: Provenance) -> CompileProvenance {
754 match provenance {
755 Provenance::ExactAst | Provenance::LiteralRequireImport => CompileProvenance::ExactAst,
757 Provenance::DesugaredAst
758 | Provenance::SemanticAnalyzer
759 | Provenance::FrameworkSynthesis
760 | Provenance::ImportExportInference
761 | Provenance::PragmaInference => CompileProvenance::DesugaredAst,
762 Provenance::NameHeuristic | Provenance::SearchFallback | Provenance::DynamicBoundary => {
763 CompileProvenance::DynamicBoundary
764 }
765 }
766}
767
768fn fact_confidence_to_compile(confidence: Confidence) -> CompileConfidence {
769 match confidence {
770 Confidence::High => CompileConfidence::High,
771 Confidence::Medium => CompileConfidence::Medium,
772 Confidence::Low => CompileConfidence::Low,
773 }
774}
775
776#[derive(Debug, Clone, PartialEq, Eq, Default)]
782#[non_exhaustive]
783pub struct ScopeGraph {
784 pub scopes: Vec<ScopeFrame>,
786 pub bindings: Vec<Binding>,
788 pub references: Vec<BindingReference>,
790}
791
792impl ScopeGraph {
793 #[inline]
795 pub fn root_scope(&self) -> Option<&ScopeFrame> {
796 self.scopes.first()
797 }
798}
799
800#[derive(Debug, Clone, PartialEq, Eq)]
802#[non_exhaustive]
803pub struct ScopeFrame {
804 pub id: HirScopeId,
806 pub parent: Option<HirScopeId>,
808 pub kind: ScopeKind,
810 pub range: SourceLocation,
812 pub package_context: Option<String>,
814}
815
816#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
818#[non_exhaustive]
819pub enum ScopeKind {
820 File,
822 Package,
824 Block,
826 Subroutine,
828 Method,
830 Signature,
832 Format,
834 EvalString,
836 PhaseBlock,
838}
839
840#[derive(Debug, Clone, PartialEq, Eq)]
842#[non_exhaustive]
843pub struct Binding {
844 pub id: HirBindingId,
846 pub scope_id: HirScopeId,
848 pub sigil: String,
850 pub name: String,
852 pub range: SourceLocation,
854 pub storage: StorageClass,
856 pub package_context: Option<String>,
858 pub declaration_item: Option<HirId>,
860 pub shadows: Option<HirBindingId>,
862}
863
864#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
866#[non_exhaustive]
867pub enum StorageClass {
868 LexicalMy,
870 LexicalState,
872 PackageOur,
874 LocalizedPackage,
876 Parameter,
878 MethodInvocant,
880 Implicit,
882 PackageGlobal,
884}
885
886#[derive(Debug, Clone, PartialEq, Eq)]
888#[non_exhaustive]
889pub struct BindingReference {
890 pub scope_id: HirScopeId,
892 pub sigil: String,
894 pub name: String,
896 pub range: SourceLocation,
898 pub resolved_binding: Option<HirBindingId>,
900}
901
902#[derive(Debug, Clone, PartialEq, Eq, Default)]
907#[non_exhaustive]
908pub struct StashGraph {
909 pub packages: Vec<PackageStash>,
911 pub inheritance_edges: Vec<PackageInheritanceEdge>,
913 pub export_declarations: Vec<ExportDeclaration>,
915 pub dynamic_boundaries: Vec<StashDynamicBoundary>,
917}
918
919impl StashGraph {
920 #[must_use]
925 pub fn export_sets(&self) -> Vec<ExportSet> {
926 let mut builders = BTreeMap::<String, ExportSetBuilder>::new();
927
928 for declaration in &self.export_declarations {
929 let builder = builders.entry(declaration.package.clone()).or_insert_with(|| {
930 ExportSetBuilder::new(
931 declaration.package.clone(),
932 declaration.range,
933 stash_provenance_to_fact(declaration.provenance),
934 stash_confidence_to_fact(declaration.confidence),
935 )
936 });
937 builder.absorb(declaration);
938 }
939
940 builders.into_values().map(ExportSetBuilder::into_export_set).collect()
941 }
942}
943
944#[derive(Debug)]
945struct ExportSetBuilder {
946 module_name: String,
947 anchor_range: SourceLocation,
948 default_exports: Vec<String>,
949 optional_exports: Vec<String>,
950 tags: BTreeMap<String, Vec<String>>,
951 provenance: Provenance,
952 confidence: Confidence,
953}
954
955impl ExportSetBuilder {
956 fn new(
957 module_name: String,
958 anchor_range: SourceLocation,
959 provenance: Provenance,
960 confidence: Confidence,
961 ) -> Self {
962 Self {
963 module_name,
964 anchor_range,
965 default_exports: Vec::new(),
966 optional_exports: Vec::new(),
967 tags: BTreeMap::new(),
968 provenance,
969 confidence,
970 }
971 }
972
973 fn absorb(&mut self, declaration: &ExportDeclaration) {
974 if declaration.range.start < self.anchor_range.start {
975 self.anchor_range = declaration.range;
976 }
977 self.provenance =
978 combine_provenance(self.provenance, stash_provenance_to_fact(declaration.provenance));
979 self.confidence =
980 combine_confidence(self.confidence, stash_confidence_to_fact(declaration.confidence));
981
982 match declaration.kind {
983 ExportDeclarationKind::Default => {
984 self.default_exports.extend(declaration.symbols.iter().cloned());
985 }
986 ExportDeclarationKind::Optional => {
987 self.optional_exports.extend(declaration.symbols.iter().cloned());
988 }
989 ExportDeclarationKind::Tag => {
990 if let Some(tag_name) = &declaration.tag_name {
991 self.tags
992 .entry(tag_name.clone())
993 .or_default()
994 .extend(declaration.symbols.iter().cloned());
995 }
996 }
997 }
998 }
999
1000 fn into_export_set(mut self) -> ExportSet {
1001 sort_dedup(&mut self.default_exports);
1002 sort_dedup(&mut self.optional_exports);
1003
1004 let tags = self
1005 .tags
1006 .into_iter()
1007 .map(|(name, mut members)| {
1008 sort_dedup(&mut members);
1009 ExportTag { name, members }
1010 })
1011 .collect();
1012
1013 ExportSet {
1014 default_exports: self.default_exports,
1015 optional_exports: self.optional_exports,
1016 tags,
1017 provenance: self.provenance,
1018 confidence: self.confidence,
1019 module_name: Some(self.module_name),
1020 anchor_id: Some(AnchorId(self.anchor_range.start as u64)),
1021 }
1022 }
1023}
1024
1025fn sort_dedup(values: &mut Vec<String>) {
1026 values.sort();
1027 values.dedup();
1028}
1029
1030fn combine_provenance(current: Provenance, next: Provenance) -> Provenance {
1031 if current == Provenance::DynamicBoundary || next == Provenance::DynamicBoundary {
1032 Provenance::DynamicBoundary
1033 } else if current == Provenance::ImportExportInference
1034 || next == Provenance::ImportExportInference
1035 {
1036 Provenance::ImportExportInference
1037 } else {
1038 current
1039 }
1040}
1041
1042fn combine_confidence(current: Confidence, next: Confidence) -> Confidence {
1043 match (current, next) {
1044 (Confidence::Low, _) | (_, Confidence::Low) => Confidence::Low,
1045 (Confidence::Medium, _) | (_, Confidence::Medium) => Confidence::Medium,
1046 (Confidence::High, Confidence::High) => Confidence::High,
1047 }
1048}
1049
1050fn stash_provenance_to_fact(provenance: StashProvenance) -> Provenance {
1051 match provenance {
1052 StashProvenance::ExactAst => Provenance::ExactAst,
1053 StashProvenance::DesugaredAst => Provenance::DesugaredAst,
1054 StashProvenance::DynamicBoundary => Provenance::DynamicBoundary,
1055 }
1056}
1057
1058fn stash_confidence_to_fact(confidence: StashConfidence) -> Confidence {
1059 match confidence {
1060 StashConfidence::High => Confidence::High,
1061 StashConfidence::Medium => Confidence::Medium,
1062 StashConfidence::Low => Confidence::Low,
1063 }
1064}
1065
1066#[derive(Debug, Clone, PartialEq, Eq)]
1068#[non_exhaustive]
1069pub struct PackageStash {
1070 pub package: String,
1072 pub range: SourceLocation,
1074 pub declaration_item: Option<HirId>,
1076 pub slots: Vec<GlobSlot>,
1078 pub provenance: StashProvenance,
1080 pub confidence: StashConfidence,
1082}
1083
1084#[derive(Debug, Clone, PartialEq, Eq)]
1086#[non_exhaustive]
1087pub struct GlobSlot {
1088 pub name: String,
1090 pub kind: GlobSlotKind,
1092 pub range: SourceLocation,
1094 pub declaration_item: Option<HirId>,
1096 pub source: GlobSlotSource,
1098 pub alias_target: Option<String>,
1100 pub provenance: StashProvenance,
1102 pub confidence: StashConfidence,
1104}
1105
1106#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1108#[non_exhaustive]
1109pub enum GlobSlotKind {
1110 Scalar,
1112 Array,
1114 Hash,
1116 Code,
1118 Io,
1120 Format,
1122}
1123
1124#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1126#[non_exhaustive]
1127pub enum GlobSlotSource {
1128 PackageDeclaration,
1130 SubDeclaration,
1132 MethodDeclaration,
1134 OurDeclaration,
1136 FormatDeclaration,
1138 ConstantDeclaration,
1140 PackageAssignment,
1142 TypeglobAlias,
1144}
1145
1146#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1148#[non_exhaustive]
1149pub enum StashProvenance {
1150 ExactAst,
1152 DesugaredAst,
1154 DynamicBoundary,
1156}
1157
1158#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1160#[non_exhaustive]
1161pub enum StashConfidence {
1162 High,
1164 Medium,
1166 Low,
1168}
1169
1170#[derive(Debug, Clone, PartialEq, Eq)]
1172#[non_exhaustive]
1173pub struct PackageInheritanceEdge {
1174 pub from_package: String,
1176 pub to_package: String,
1178 pub range: SourceLocation,
1180 pub declaration_item: Option<HirId>,
1182 pub source: InheritanceSource,
1184 pub provenance: StashProvenance,
1186 pub confidence: StashConfidence,
1188}
1189
1190#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1192#[non_exhaustive]
1193pub enum InheritanceSource {
1194 IsaAssignment,
1196 UseParent,
1198 UseBase,
1200}
1201
1202#[derive(Debug, Clone, PartialEq, Eq)]
1204#[non_exhaustive]
1205pub struct ExportDeclaration {
1206 pub package: String,
1208 pub kind: ExportDeclarationKind,
1210 pub tag_name: Option<String>,
1212 pub symbols: Vec<String>,
1214 pub range: SourceLocation,
1216 pub declaration_item: Option<HirId>,
1218 pub provenance: StashProvenance,
1220 pub confidence: StashConfidence,
1222}
1223
1224#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1226#[non_exhaustive]
1227pub enum ExportDeclarationKind {
1228 Default,
1230 Optional,
1232 Tag,
1234}
1235
1236#[derive(Debug, Clone, PartialEq, Eq)]
1238#[non_exhaustive]
1239pub struct StashDynamicBoundary {
1240 pub package: Option<String>,
1242 pub symbol: Option<String>,
1244 pub range: SourceLocation,
1246 pub boundary_item: Option<HirId>,
1248 pub kind: StashDynamicBoundaryKind,
1250 pub reason: String,
1252 pub provenance: StashProvenance,
1254 pub confidence: StashConfidence,
1256}
1257
1258#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1260#[non_exhaustive]
1261pub enum StashDynamicBoundaryKind {
1262 DynamicStashMutation,
1264 DynamicExportDeclaration,
1266 Autoload,
1268}
1269
1270#[derive(Debug, Clone, PartialEq, Eq, Default)]
1276#[non_exhaustive]
1277pub struct CompileEnvironment {
1278 pub directives: Vec<CompileDirective>,
1280 pub pragma_effects: Vec<PragmaEffect>,
1282 pub pragma_state_facts: Vec<PragmaStateFact>,
1284 pub inc_roots: Vec<IncRootFact>,
1286 pub module_requests: Vec<ModuleRequest>,
1288 pub phase_blocks: Vec<CompilePhaseBlock>,
1290 pub dynamic_boundaries: Vec<CompileEnvironmentBoundary>,
1292}
1293
1294impl CompileEnvironment {
1295 #[must_use]
1297 pub fn pragma_state_facts(&self) -> &[PragmaStateFact] {
1298 &self.pragma_state_facts
1299 }
1300
1301 #[must_use]
1303 pub fn pragma_state_at(&self, offset: usize) -> Option<&PragmaStateFact> {
1304 let idx = self.pragma_state_facts.partition_point(|fact| fact.range.start <= offset);
1305 if idx > 0 { self.pragma_state_facts.get(idx - 1) } else { None }
1306 }
1307
1308 #[must_use]
1313 pub fn import_specs(&self, file_id: FileId) -> Vec<ImportSpec> {
1314 self.directives
1315 .iter()
1316 .filter_map(|directive| import_spec_from_directive(directive, file_id))
1317 .collect()
1318 }
1319
1320 #[must_use]
1328 pub fn module_resolution_candidates(
1329 &self,
1330 supplied_roots: &[ModuleResolutionRoot],
1331 ) -> Vec<ModuleResolutionCandidate> {
1332 self.module_requests
1333 .iter()
1334 .enumerate()
1335 .filter_map(|(request_index, request)| {
1336 let target = request.target.as_ref()?;
1337 let normalized_target = normalize_module_target(target);
1338 let relative_path = module_target_to_relative_path(&normalized_target)?;
1339 let candidate_roots =
1340 self.candidate_roots_for_request(request, &relative_path, supplied_roots);
1341 let status = if candidate_roots.is_empty() {
1342 ModuleResolutionCandidateStatus::NotFound
1343 } else {
1344 ModuleResolutionCandidateStatus::CandidateBuilt
1345 };
1346
1347 Some(ModuleResolutionCandidate {
1348 request_index,
1349 directive_item: request.directive_item,
1350 request_kind: request.kind,
1351 target: normalized_target,
1352 relative_path,
1353 roots: candidate_roots,
1354 status,
1355 resolved_path: None,
1356 range: request.range,
1357 package_context: request.package_context.clone(),
1358 provenance: request.provenance,
1359 confidence: request.confidence,
1360 })
1361 })
1362 .collect()
1363 }
1364
1365 #[must_use]
1371 pub fn resolved_module_resolution_candidates(
1372 &self,
1373 supplied_roots: &[ModuleResolutionRoot],
1374 mut path_exists: impl FnMut(&str) -> bool,
1375 ) -> Vec<ModuleResolutionCandidate> {
1376 let mut candidates = self.module_resolution_candidates(supplied_roots);
1377
1378 for candidate in &mut candidates {
1379 if candidate.status != ModuleResolutionCandidateStatus::CandidateBuilt {
1380 continue;
1381 }
1382
1383 if let Some(root) =
1384 candidate.roots.iter().find(|root| path_exists(&root.candidate_path))
1385 {
1386 candidate.status = ModuleResolutionCandidateStatus::Resolved;
1387 candidate.resolved_path = Some(root.candidate_path.clone());
1388 } else {
1389 candidate.status = ModuleResolutionCandidateStatus::NotFound;
1390 }
1391 }
1392
1393 candidates
1394 }
1395
1396 fn candidate_roots_for_request(
1397 &self,
1398 request: &ModuleRequest,
1399 relative_path: &str,
1400 supplied_roots: &[ModuleResolutionRoot],
1401 ) -> Vec<ModuleResolutionCandidateRoot> {
1402 let active_lexical_roots = self.active_lexical_roots_for_request(request);
1403 active_lexical_roots
1404 .iter()
1405 .map(|root| ModuleResolutionRoot {
1406 path: root.path.clone(),
1407 kind: root.kind,
1408 source: root.source.clone(),
1409 })
1410 .chain(supplied_roots.iter().cloned())
1411 .enumerate()
1412 .map(|(precedence, root)| ModuleResolutionCandidateRoot {
1413 path: root.path.clone(),
1414 kind: root.kind,
1415 source: root.source,
1416 candidate_path: join_candidate_path(&root.path, relative_path),
1417 precedence,
1418 })
1419 .collect()
1420 }
1421
1422 fn active_lexical_roots_for_request(&self, request: &ModuleRequest) -> Vec<ActiveLexicalRoot> {
1423 let mut active = Vec::new();
1424
1425 for (order, root) in self.inc_roots.iter().enumerate() {
1426 if root.range.start > request.range.start {
1427 continue;
1428 }
1429 if root.kind != IncRootKind::UseLib {
1430 continue;
1431 }
1432
1433 match root.action {
1434 IncRootAction::Add => {
1435 active.push(ActiveLexicalRoot {
1436 path: root.path.clone(),
1437 kind: root.kind,
1438 source: "use-lib-lexical".to_string(),
1439 range_start: root.range.start,
1440 order,
1441 });
1442 }
1443 IncRootAction::Remove => {
1444 active.retain(|active_root| active_root.path != root.path);
1445 }
1446 }
1447 }
1448
1449 active.sort_by(|left, right| {
1450 right.range_start.cmp(&left.range_start).then_with(|| left.order.cmp(&right.order))
1451 });
1452
1453 active
1454 }
1455}
1456
1457#[derive(Debug, Clone, PartialEq, Eq)]
1458struct ActiveLexicalRoot {
1459 path: String,
1460 kind: IncRootKind,
1461 source: String,
1462 range_start: usize,
1463 order: usize,
1464}
1465
1466#[derive(Debug, Clone, PartialEq, Eq)]
1468#[non_exhaustive]
1469pub struct CompileDirective {
1470 pub action: CompileDirectiveAction,
1472 pub module: Option<String>,
1474 pub args: Vec<String>,
1476 pub range: SourceLocation,
1478 pub item_id: Option<HirId>,
1480 pub scope_id: Option<HirScopeId>,
1482 pub package_context: Option<String>,
1484 pub kind: CompileDirectiveKind,
1486 pub provenance: CompileProvenance,
1488 pub confidence: CompileConfidence,
1490}
1491
1492#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1494#[non_exhaustive]
1495pub enum CompileDirectiveAction {
1496 Use,
1498 No,
1500 Require,
1502}
1503
1504#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1506#[non_exhaustive]
1507pub enum CompileDirectiveKind {
1508 Strict,
1510 Warnings,
1512 Feature,
1514 Lib,
1516 Inheritance,
1518 Constant,
1520 Module,
1522 Dynamic,
1524}
1525
1526#[derive(Debug, Clone, PartialEq, Eq)]
1528#[non_exhaustive]
1529pub struct PragmaEffect {
1530 pub pragma: String,
1532 pub enabled: bool,
1534 pub args: Vec<String>,
1536 pub argument_kind: PragmaArgumentKind,
1538 pub range: SourceLocation,
1540 pub directive_item: Option<HirId>,
1542 pub scope_id: Option<HirScopeId>,
1544 pub package_context: Option<String>,
1546 pub provenance: CompileProvenance,
1548 pub confidence: CompileConfidence,
1550}
1551
1552#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1554#[non_exhaustive]
1555pub enum PragmaArgumentKind {
1556 Broad,
1558 Categories,
1560}
1561
1562#[derive(Debug, Clone, PartialEq, Eq)]
1564#[non_exhaustive]
1565pub struct PragmaStateFact {
1566 pub range: SourceLocation,
1568 pub anchor_id: AnchorId,
1570 pub directive_item: Option<HirId>,
1572 pub scope_id: Option<HirScopeId>,
1574 pub package_context: Option<String>,
1576 pub strict_vars: bool,
1578 pub strict_subs: bool,
1580 pub strict_refs: bool,
1582 pub warnings: bool,
1584 pub disabled_warning_categories: Vec<String>,
1586 pub features: Vec<String>,
1588 pub provenance: CompileProvenance,
1590 pub confidence: CompileConfidence,
1592}
1593
1594impl PragmaStateFact {
1595 #[must_use]
1597 pub fn strict_enabled(&self) -> bool {
1598 self.strict_vars && self.strict_subs && self.strict_refs
1599 }
1600
1601 #[must_use]
1603 pub fn warning_active(&self, category: &str) -> bool {
1604 self.warnings && !self.disabled_warning_categories.iter().any(|name| name == category)
1605 }
1606
1607 #[must_use]
1609 pub fn has_feature(&self, feature: &str) -> bool {
1610 self.features.iter().any(|name| name == feature)
1611 }
1612}
1613
1614#[derive(Debug, Clone, PartialEq, Eq)]
1620#[non_exhaustive]
1621pub struct FrameworkAdapterRegistry {
1622 adapters: Vec<FrameworkAdapterKind>,
1623}
1624
1625impl Default for FrameworkAdapterRegistry {
1626 fn default() -> Self {
1627 Self { adapters: vec![FrameworkAdapterKind::ExporterFamily] }
1628 }
1629}
1630
1631impl FrameworkAdapterRegistry {
1632 #[must_use]
1634 pub fn new(adapters: Vec<FrameworkAdapterKind>) -> Self {
1635 Self { adapters }
1636 }
1637
1638 #[must_use]
1640 pub fn adapters(&self) -> &[FrameworkAdapterKind] {
1641 &self.adapters
1642 }
1643
1644 #[must_use]
1646 pub fn project_file(&self, file: &HirFile) -> FrameworkFactGraph {
1647 let mut graph = FrameworkFactGraph::default();
1648
1649 for adapter in &self.adapters {
1650 match adapter {
1651 FrameworkAdapterKind::ExporterFamily => {
1652 project_exporter_family_facts(file, &mut graph);
1653 }
1654 }
1655 }
1656
1657 graph
1658 }
1659}
1660
1661#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1663#[non_exhaustive]
1664pub enum FrameworkAdapterKind {
1665 ExporterFamily,
1667}
1668
1669#[derive(Debug, Clone, PartialEq, Eq, Default)]
1671#[non_exhaustive]
1672pub struct FrameworkFactGraph {
1673 pub exported_symbols: Vec<FrameworkExportedSymbolFact>,
1675 pub dynamic_boundaries: Vec<FrameworkDynamicBoundaryFact>,
1677}
1678
1679#[derive(Debug, Clone, PartialEq, Eq)]
1681#[non_exhaustive]
1682pub struct FrameworkExportedSymbolFact {
1683 pub adapter: FrameworkAdapterKind,
1685 pub package: String,
1687 pub name: String,
1689 pub kind: FrameworkExportedSymbolKind,
1691 pub tag_name: Option<String>,
1693 pub range: SourceLocation,
1695 pub declaration_item: Option<HirId>,
1697 pub visible_symbol: VisibleSymbol,
1699 pub source_provenance: Provenance,
1701 pub source_confidence: Confidence,
1703 pub provenance: Provenance,
1705 pub confidence: Confidence,
1707}
1708
1709#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1711#[non_exhaustive]
1712pub enum FrameworkExportedSymbolKind {
1713 Default,
1715 Optional,
1717 TagMember,
1719}
1720
1721#[derive(Debug, Clone, PartialEq, Eq)]
1723#[non_exhaustive]
1724pub struct FrameworkDynamicBoundaryFact {
1725 pub adapter: FrameworkAdapterKind,
1727 pub package: Option<String>,
1729 pub symbol: Option<String>,
1731 pub range: SourceLocation,
1733 pub boundary_item: Option<HirId>,
1735 pub kind: StashDynamicBoundaryKind,
1737 pub reason: String,
1739 pub provenance: Provenance,
1741 pub confidence: Confidence,
1743}
1744
1745fn project_exporter_family_facts(file: &HirFile, graph: &mut FrameworkFactGraph) {
1746 for declaration in &file.stash_graph.export_declarations {
1747 match declaration.kind {
1748 ExportDeclarationKind::Default => {
1749 project_exported_symbols_from_declaration(
1750 declaration,
1751 FrameworkExportedSymbolKind::Default,
1752 None,
1753 graph,
1754 );
1755 }
1756 ExportDeclarationKind::Optional => {
1757 project_exported_symbols_from_declaration(
1758 declaration,
1759 FrameworkExportedSymbolKind::Optional,
1760 None,
1761 graph,
1762 );
1763 }
1764 ExportDeclarationKind::Tag => {
1765 project_exported_symbols_from_declaration(
1766 declaration,
1767 FrameworkExportedSymbolKind::TagMember,
1768 declaration.tag_name.as_deref(),
1769 graph,
1770 );
1771 }
1772 }
1773 }
1774
1775 for boundary in &file.stash_graph.dynamic_boundaries {
1776 if boundary.kind != StashDynamicBoundaryKind::DynamicExportDeclaration {
1777 continue;
1778 }
1779 graph.dynamic_boundaries.push(FrameworkDynamicBoundaryFact {
1780 adapter: FrameworkAdapterKind::ExporterFamily,
1781 package: boundary.package.clone(),
1782 symbol: boundary.symbol.clone(),
1783 range: boundary.range,
1784 boundary_item: boundary.boundary_item,
1785 kind: boundary.kind,
1786 reason: boundary.reason.clone(),
1787 provenance: Provenance::DynamicBoundary,
1788 confidence: Confidence::Low,
1789 });
1790 }
1791}
1792
1793fn project_exported_symbols_from_declaration(
1794 declaration: &ExportDeclaration,
1795 kind: FrameworkExportedSymbolKind,
1796 tag_name: Option<&str>,
1797 graph: &mut FrameworkFactGraph,
1798) {
1799 for symbol in &declaration.symbols {
1800 graph.exported_symbols.push(FrameworkExportedSymbolFact {
1801 adapter: FrameworkAdapterKind::ExporterFamily,
1802 package: declaration.package.clone(),
1803 name: symbol.clone(),
1804 kind,
1805 tag_name: tag_name.map(str::to_string),
1806 range: declaration.range,
1807 declaration_item: declaration.declaration_item,
1808 visible_symbol: visible_symbol_for_export(declaration, symbol, kind),
1809 source_provenance: stash_provenance_to_fact(declaration.provenance),
1810 source_confidence: stash_confidence_to_fact(declaration.confidence),
1811 provenance: Provenance::FrameworkSynthesis,
1812 confidence: Confidence::Medium,
1813 });
1814 }
1815}
1816
1817fn visible_symbol_for_export(
1818 declaration: &ExportDeclaration,
1819 symbol: &str,
1820 kind: FrameworkExportedSymbolKind,
1821) -> VisibleSymbol {
1822 let source = match kind {
1823 FrameworkExportedSymbolKind::Default => VisibleSymbolSource::DefaultExport,
1824 FrameworkExportedSymbolKind::Optional => VisibleSymbolSource::ExplicitImport,
1825 FrameworkExportedSymbolKind::TagMember => VisibleSymbolSource::ExportTag,
1826 };
1827
1828 VisibleSymbol {
1829 name: symbol.to_string(),
1830 entity_id: None,
1831 source,
1832 confidence: Confidence::Medium,
1833 context: Some(VisibleSymbolContext::new(
1834 Some(declaration.package.clone()),
1835 None,
1836 Some(AnchorId(declaration.range.start as u64)),
1837 )),
1838 }
1839}
1840
1841#[derive(Debug, Clone, PartialEq, Eq)]
1843#[non_exhaustive]
1844pub struct IncRootFact {
1845 pub path: String,
1847 pub action: IncRootAction,
1849 pub kind: IncRootKind,
1851 pub range: SourceLocation,
1853 pub directive_item: Option<HirId>,
1855 pub scope_id: Option<HirScopeId>,
1857 pub package_context: Option<String>,
1859 pub provenance: CompileProvenance,
1861 pub confidence: CompileConfidence,
1863}
1864
1865#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1867#[non_exhaustive]
1868pub enum IncRootAction {
1869 Add,
1871 Remove,
1873}
1874
1875#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1877#[non_exhaustive]
1878pub enum IncRootKind {
1879 UseLib,
1881 Configured,
1883 Perl5Lib,
1885 SystemInc,
1887}
1888
1889#[derive(Debug, Clone, PartialEq, Eq)]
1891#[non_exhaustive]
1892pub struct ModuleResolutionRoot {
1893 pub path: String,
1895 pub kind: IncRootKind,
1897 pub source: String,
1899}
1900
1901impl ModuleResolutionRoot {
1902 #[must_use]
1904 pub fn new(path: impl Into<String>, kind: IncRootKind, source: impl Into<String>) -> Self {
1905 Self { path: path.into(), kind, source: source.into() }
1906 }
1907}
1908
1909#[derive(Debug, Clone, PartialEq, Eq)]
1911#[non_exhaustive]
1912pub struct ModuleRequest {
1913 pub target: Option<String>,
1915 pub kind: ModuleRequestKind,
1917 pub range: SourceLocation,
1919 pub directive_item: Option<HirId>,
1921 pub scope_id: Option<HirScopeId>,
1923 pub package_context: Option<String>,
1925 pub resolution: ModuleResolutionStatus,
1927 pub provenance: CompileProvenance,
1929 pub confidence: CompileConfidence,
1931}
1932
1933#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1935#[non_exhaustive]
1936pub enum ModuleRequestKind {
1937 Use,
1939 Require,
1941 Parent,
1943 Base,
1945}
1946
1947#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1949#[non_exhaustive]
1950pub enum ModuleResolutionStatus {
1951 Deferred,
1953 Dynamic,
1955}
1956
1957#[derive(Debug, Clone, PartialEq, Eq)]
1959#[non_exhaustive]
1960pub struct ModuleResolutionCandidate {
1961 pub request_index: usize,
1963 pub directive_item: Option<HirId>,
1965 pub request_kind: ModuleRequestKind,
1967 pub target: String,
1969 pub relative_path: String,
1971 pub roots: Vec<ModuleResolutionCandidateRoot>,
1973 pub status: ModuleResolutionCandidateStatus,
1975 pub resolved_path: Option<String>,
1977 pub range: SourceLocation,
1979 pub package_context: Option<String>,
1981 pub provenance: CompileProvenance,
1983 pub confidence: CompileConfidence,
1985}
1986
1987impl ModuleResolutionCandidate {
1988 #[must_use]
1996 pub fn cache_key(&self, resolver_epoch: u64) -> ModuleResolutionCacheKey {
1997 ModuleResolutionCacheKey {
1998 resolver_epoch,
1999 request_index: self.request_index,
2000 directive_item: self.directive_item,
2001 request_kind: self.request_kind,
2002 target: self.target.clone(),
2003 relative_path: self.relative_path.clone(),
2004 roots: self
2005 .roots
2006 .iter()
2007 .map(ModuleResolutionCacheRootKey::from_candidate_root)
2008 .collect(),
2009 range: self.range,
2010 package_context: self.package_context.clone(),
2011 }
2012 }
2013
2014 #[must_use]
2019 pub fn cache_invalidation(
2020 &self,
2021 resolver_epoch: u64,
2022 mut path_exists: impl FnMut(&str) -> bool,
2023 ) -> ModuleResolutionCacheInvalidation {
2024 let path_states = self
2025 .roots
2026 .iter()
2027 .map(|root| ModuleResolutionCandidatePathState {
2028 candidate_path: root.candidate_path.clone(),
2029 exists: path_exists(&root.candidate_path),
2030 })
2031 .collect();
2032
2033 ModuleResolutionCacheInvalidation { key: self.cache_key(resolver_epoch), path_states }
2034 }
2035}
2036
2037#[derive(Debug, Clone, PartialEq, Eq)]
2039#[non_exhaustive]
2040pub struct ModuleResolutionCandidateRoot {
2041 pub path: String,
2043 pub kind: IncRootKind,
2045 pub source: String,
2047 pub candidate_path: String,
2049 pub precedence: usize,
2051}
2052
2053#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2055#[non_exhaustive]
2056pub struct ModuleResolutionCacheKey {
2057 pub resolver_epoch: u64,
2059 pub request_index: usize,
2061 pub directive_item: Option<HirId>,
2063 pub request_kind: ModuleRequestKind,
2065 pub target: String,
2067 pub relative_path: String,
2069 pub roots: Vec<ModuleResolutionCacheRootKey>,
2071 pub range: SourceLocation,
2073 pub package_context: Option<String>,
2075}
2076
2077#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2079#[non_exhaustive]
2080pub struct ModuleResolutionCacheRootKey {
2081 pub path: String,
2083 pub kind: IncRootKind,
2085 pub source: String,
2087 pub candidate_path: String,
2089 pub precedence: usize,
2091}
2092
2093impl ModuleResolutionCacheRootKey {
2094 fn from_candidate_root(root: &ModuleResolutionCandidateRoot) -> Self {
2095 Self {
2096 path: root.path.clone(),
2097 kind: root.kind,
2098 source: root.source.clone(),
2099 candidate_path: root.candidate_path.clone(),
2100 precedence: root.precedence,
2101 }
2102 }
2103}
2104
2105#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2107#[non_exhaustive]
2108pub struct ModuleResolutionCandidatePathState {
2109 pub candidate_path: String,
2111 pub exists: bool,
2113}
2114
2115#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2117#[non_exhaustive]
2118pub struct ModuleResolutionCacheInvalidation {
2119 pub key: ModuleResolutionCacheKey,
2121 pub path_states: Vec<ModuleResolutionCandidatePathState>,
2123}
2124
2125#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
2127#[non_exhaustive]
2128pub enum ModuleResolutionCandidateStatus {
2129 CandidateBuilt,
2131 Dynamic,
2133 NotFound,
2135 Resolved,
2137 TimedOut,
2139}
2140
2141#[derive(Debug, Clone, PartialEq, Eq)]
2143#[non_exhaustive]
2144pub struct CompilePhaseBlock {
2145 pub phase: CompilePhase,
2147 pub range: SourceLocation,
2149 pub scope_id: Option<HirScopeId>,
2151 pub package_context: Option<String>,
2153 pub provenance: CompileProvenance,
2155 pub confidence: CompileConfidence,
2157}
2158
2159#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
2161#[non_exhaustive]
2162pub enum CompilePhase {
2163 Begin,
2165 UnitCheck,
2167 Check,
2169 Init,
2171 End,
2173 Unknown,
2175}
2176
2177#[derive(Debug, Clone, PartialEq, Eq)]
2179#[non_exhaustive]
2180pub struct CompileEnvironmentBoundary {
2181 pub kind: CompileEnvironmentBoundaryKind,
2183 pub range: SourceLocation,
2185 pub boundary_item: Option<HirId>,
2187 pub scope_id: Option<HirScopeId>,
2189 pub package_context: Option<String>,
2191 pub reason: String,
2193 pub provenance: CompileProvenance,
2195 pub confidence: CompileConfidence,
2197}
2198
2199#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
2201#[non_exhaustive]
2202pub enum CompileEnvironmentBoundaryKind {
2203 DynamicRequire,
2205 DynamicPragmaArgs,
2207 DynamicIncRoot,
2209 PhaseBlockExecution,
2211 SymbolicReferenceDeref,
2213}
2214
2215#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
2217#[non_exhaustive]
2218pub enum CompileProvenance {
2219 ExactAst,
2221 DesugaredAst,
2223 DynamicBoundary,
2225}
2226
2227#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
2229#[non_exhaustive]
2230pub enum CompileConfidence {
2231 High,
2233 Medium,
2235 Low,
2237}
2238
2239fn import_spec_from_directive(directive: &CompileDirective, file_id: FileId) -> Option<ImportSpec> {
2240 match directive.action {
2241 CompileDirectiveAction::Use => {
2242 let module = directive.module.as_deref()?;
2243 if is_version_pragma(module) {
2244 return None;
2245 }
2246 if module == "constant" {
2247 return Some(classify_constant_import(directive, file_id));
2248 }
2249
2250 let (kind, symbols, provenance, confidence) =
2251 classify_import_args(&directive.args, module, directive.range);
2252 Some(import_spec(
2253 module.to_string(),
2254 kind,
2255 symbols,
2256 provenance,
2257 confidence,
2258 directive,
2259 file_id,
2260 ))
2261 }
2262 CompileDirectiveAction::Require => {
2263 let (module, kind, symbols, provenance, confidence) =
2264 if let Some(module) = directive.module.as_ref() {
2265 (
2266 module.clone(),
2267 ImportKind::Require,
2268 ImportSymbols::Default,
2269 Provenance::ExactAst,
2270 Confidence::High,
2271 )
2272 } else {
2273 (
2274 String::new(),
2275 ImportKind::DynamicRequire,
2276 ImportSymbols::Dynamic,
2277 Provenance::DynamicBoundary,
2278 Confidence::Low,
2279 )
2280 };
2281 Some(import_spec(module, kind, symbols, provenance, confidence, directive, file_id))
2282 }
2283 CompileDirectiveAction::No => None,
2284 }
2285}
2286
2287fn import_spec(
2288 module: String,
2289 kind: ImportKind,
2290 symbols: ImportSymbols,
2291 provenance: Provenance,
2292 confidence: Confidence,
2293 directive: &CompileDirective,
2294 file_id: FileId,
2295) -> ImportSpec {
2296 ImportSpec {
2297 module,
2298 kind,
2299 symbols,
2300 provenance,
2301 confidence,
2302 file_id: Some(file_id),
2303 anchor_id: Some(AnchorId(directive.range.start as u64)),
2304 scope_id: directive.scope_id.map(|id| ScopeId(id.index() as u64)),
2305 span_start_byte: Some(directive.range.start as u32),
2306 }
2307}
2308
2309fn classify_import_args(
2310 args: &[String],
2311 module: &str,
2312 range: SourceLocation,
2313) -> (ImportKind, ImportSymbols, Provenance, Confidence) {
2314 if args.is_empty() {
2315 let bare_len = "use ".len() + module.len() + 1;
2316 let span_len = range.end.saturating_sub(range.start);
2317 if span_len > bare_len {
2318 return (
2319 ImportKind::UseEmpty,
2320 ImportSymbols::None,
2321 Provenance::ExactAst,
2322 Confidence::High,
2323 );
2324 }
2325 return (ImportKind::Use, ImportSymbols::Default, Provenance::ExactAst, Confidence::High);
2326 }
2327
2328 let mut explicit_names = Vec::new();
2329 let mut tags = Vec::new();
2330 let mut has_dynamic_arg = false;
2331
2332 for arg in args {
2333 let trimmed = arg.trim();
2334 if trimmed == "=>" || trimmed == "," || trimmed == "\\" {
2335 continue;
2336 }
2337
2338 if let Some(inner) = parse_qw_content(trimmed) {
2339 collect_qw_import_words(inner, &mut explicit_names, &mut tags);
2340 continue;
2341 }
2342
2343 let was_quoted = is_quoted(trimmed);
2344 let unquoted = unquote(trimmed);
2345 if !was_quoted && looks_like_dynamic_import_arg(unquoted) {
2346 has_dynamic_arg = true;
2347 continue;
2348 }
2349
2350 if let Some(tag) = unquoted.strip_prefix(':') {
2351 tags.push(tag.to_string());
2352 continue;
2353 }
2354
2355 if looks_like_symbol_name(unquoted) {
2356 explicit_names.push(unquoted.to_string());
2357 }
2358 }
2359
2360 if has_dynamic_arg {
2361 return (
2362 ImportKind::UseExplicitList,
2363 ImportSymbols::Dynamic,
2364 Provenance::DynamicBoundary,
2365 Confidence::Low,
2366 );
2367 }
2368
2369 if !tags.is_empty() && explicit_names.is_empty() {
2370 return (
2371 ImportKind::UseTag,
2372 ImportSymbols::Tags(tags),
2373 Provenance::ExactAst,
2374 Confidence::High,
2375 );
2376 }
2377
2378 if !tags.is_empty() && !explicit_names.is_empty() {
2379 return (
2380 ImportKind::UseExplicitList,
2381 ImportSymbols::Mixed { tags, names: explicit_names },
2382 Provenance::ExactAst,
2383 Confidence::High,
2384 );
2385 }
2386
2387 if !explicit_names.is_empty() {
2388 return (
2389 ImportKind::UseExplicitList,
2390 ImportSymbols::Explicit(explicit_names),
2391 Provenance::ExactAst,
2392 Confidence::High,
2393 );
2394 }
2395
2396 (ImportKind::UseEmpty, ImportSymbols::None, Provenance::ExactAst, Confidence::High)
2397}
2398
2399fn classify_constant_import(directive: &CompileDirective, file_id: FileId) -> ImportSpec {
2400 let mut constant_names = Vec::new();
2401 let args = &directive.args;
2402
2403 if args.first().map(String::as_str) == Some("{") {
2404 let mut index = 1;
2405 while index < args.len() {
2406 let token = args[index].trim();
2407 if token == "}" || token == "=>" || token == "," {
2408 index += 1;
2409 continue;
2410 }
2411 if index + 1 < args.len() && args[index + 1].trim() == "=>" {
2412 constant_names.push(unquote(token).to_string());
2413 index += 3;
2414 } else {
2415 index += 1;
2416 }
2417 }
2418 } else if let Some(inner) = args.first().and_then(|arg| parse_qw_content(arg.trim())) {
2419 constant_names.extend(inner.split_whitespace().map(str::to_string));
2420 } else if let Some(name) = args.first() {
2421 let trimmed = unquote(name.trim());
2422 if looks_like_constant_name(trimmed) {
2423 constant_names.push(trimmed.to_string());
2424 }
2425 }
2426
2427 let mut seen = std::collections::HashSet::new();
2428 constant_names.retain(|name| seen.insert(name.clone()));
2429
2430 let symbols = if constant_names.is_empty() {
2431 ImportSymbols::None
2432 } else {
2433 ImportSymbols::Explicit(constant_names)
2434 };
2435
2436 import_spec(
2437 "constant".to_string(),
2438 ImportKind::UseConstant,
2439 symbols,
2440 Provenance::ExactAst,
2441 Confidence::High,
2442 directive,
2443 file_id,
2444 )
2445}
2446
2447fn collect_qw_import_words(inner: &str, explicit_names: &mut Vec<String>, tags: &mut Vec<String>) {
2448 for word in inner.split_whitespace() {
2449 if let Some(tag) = word.strip_prefix(':') {
2450 tags.push(tag.to_string());
2451 } else {
2452 explicit_names.push(word.to_string());
2453 }
2454 }
2455}
2456
2457fn is_version_pragma(module: &str) -> bool {
2458 if module.chars().next().is_some_and(|character| character.is_ascii_digit()) {
2459 return true;
2460 }
2461 module.starts_with('v')
2462 && module.len() > 1
2463 && module[1..].chars().all(|character| character.is_ascii_digit() || character == '.')
2464}
2465
2466fn parse_qw_content(value: &str) -> Option<&str> {
2467 let rest = value.strip_prefix("qw")?.trim_start();
2468 let mut chars = rest.chars();
2469 let open = chars.next()?;
2470 let close = match open {
2471 '(' => ')',
2472 '[' => ']',
2473 '{' => '}',
2474 '<' => '>',
2475 other => other,
2476 };
2477 let inner_start = open.len_utf8();
2478 let inner_end = rest.len().checked_sub(close.len_utf8())?;
2479 if inner_start > inner_end || !rest.ends_with(close) {
2480 return None;
2481 }
2482 Some(&rest[inner_start..inner_end])
2483}
2484
2485fn is_quoted(value: &str) -> bool {
2486 (value.starts_with('\'') && value.ends_with('\''))
2487 || (value.starts_with('"') && value.ends_with('"'))
2488}
2489
2490fn unquote(value: &str) -> &str {
2491 if is_quoted(value) && value.len() >= 2 { &value[1..value.len() - 1] } else { value }
2492}
2493
2494fn looks_like_dynamic_import_arg(value: &str) -> bool {
2495 value.starts_with('$')
2496 || value.starts_with('@')
2497 || value.starts_with('%')
2498 || value.starts_with('&')
2499 || value.starts_with('*')
2500}
2501
2502fn looks_like_symbol_name(value: &str) -> bool {
2503 let value = unquote(value);
2504 if value.is_empty() {
2505 return false;
2506 }
2507 if value.starts_with(':') {
2508 return true;
2509 }
2510 value
2511 .chars()
2512 .next()
2513 .is_some_and(|character| character.is_ascii_alphabetic() || character == '_')
2514}
2515
2516fn looks_like_constant_name(value: &str) -> bool {
2517 if value.is_empty() {
2518 return false;
2519 }
2520 value
2521 .chars()
2522 .next()
2523 .is_some_and(|character| character.is_ascii_alphabetic() || character == '_')
2524}
2525
2526fn module_target_to_relative_path(target: &str) -> Option<String> {
2527 let relative_path =
2528 if target.ends_with(".pm") || target.ends_with(".pl") || target.contains(['/', '\\']) {
2529 target.replace('\\', "/")
2530 } else {
2531 let canonical = target.replace('\'', "::");
2532 format!("{}.pm", canonical.replace("::", "/"))
2533 };
2534
2535 is_safe_relative_module_path(&relative_path).then_some(relative_path)
2536}
2537
2538fn normalize_module_target(target: &str) -> String {
2539 target.trim().trim_matches('"').trim_matches('\'').to_string()
2540}
2541
2542fn is_safe_relative_module_path(path: &str) -> bool {
2543 if path.is_empty() || path.starts_with('/') || path.contains(':') {
2544 return false;
2545 }
2546
2547 path.split('/').all(|segment| !matches!(segment, "" | "." | ".."))
2548}
2549
2550fn join_candidate_path(root: &str, relative_path: &str) -> String {
2551 let normalized_root = root.replace('\\', "/");
2552 let trimmed_root = normalized_root.trim_end_matches('/');
2553 if trimmed_root.is_empty() {
2554 relative_path.to_string()
2555 } else {
2556 format!("{trimmed_root}/{relative_path}")
2557 }
2558}
2559
2560#[derive(Debug, Clone, PartialEq, Eq)]
2562#[non_exhaustive]
2563pub enum HirKind {
2564 PackageDecl(PackageDecl),
2566 SubDecl(SubDecl),
2568 MethodDecl(MethodDecl),
2570 UseDecl(UseDecl),
2572 RequireDecl(RequireDecl),
2574 VariableDecl(VariableDecl),
2576 CallExpr(CallExpr),
2578 MethodCallExpr(MethodCallExpr),
2580 IndirectCallExpr(IndirectCallExpr),
2582 BarewordExpr(BarewordExpr),
2584 LiteralExpr(LiteralExpr),
2586 BlockShell(BlockShell),
2588 DynamicBoundary(DynamicBoundary),
2590}
2591
2592impl HirKind {
2593 pub const ALL_KIND_NAMES: &[&'static str] = &[
2598 "BarewordExpr",
2599 "BlockShell",
2600 "CallExpr",
2601 "DynamicBoundary",
2602 "IndirectCallExpr",
2603 "LiteralExpr",
2604 "MethodCallExpr",
2605 "MethodDecl",
2606 "PackageDecl",
2607 "RequireDecl",
2608 "SubDecl",
2609 "UseDecl",
2610 "VariableDecl",
2611 ];
2612}
2613
2614#[derive(Debug, Clone, PartialEq, Eq)]
2616#[non_exhaustive]
2617pub struct PackageDecl {
2618 pub name: String,
2620 pub name_range: SourceLocation,
2622 pub has_block: bool,
2624}
2625
2626#[derive(Debug, Clone, PartialEq, Eq)]
2628#[non_exhaustive]
2629pub struct SubDecl {
2630 pub name: Option<String>,
2632 pub name_range: Option<SourceLocation>,
2634 pub has_prototype: bool,
2636 pub has_signature: bool,
2638 pub attribute_count: usize,
2640}
2641
2642#[derive(Debug, Clone, PartialEq, Eq)]
2644#[non_exhaustive]
2645pub struct MethodDecl {
2646 pub name: String,
2648 pub has_signature: bool,
2650 pub attribute_count: usize,
2652}
2653
2654#[derive(Debug, Clone, PartialEq, Eq)]
2656#[non_exhaustive]
2657pub struct UseDecl {
2658 pub module: String,
2660 pub args: Vec<String>,
2662 pub has_filter_risk: bool,
2664}
2665
2666#[derive(Debug, Clone, PartialEq, Eq)]
2668#[non_exhaustive]
2669pub struct RequireDecl {
2670 pub target: Option<String>,
2672 pub arg_count: usize,
2674}
2675
2676#[derive(Debug, Clone, PartialEq, Eq)]
2678#[non_exhaustive]
2679pub struct VariableDecl {
2680 pub declarator: String,
2682 pub variables: Vec<VariableBinding>,
2684 pub attribute_count: usize,
2686 pub has_initializer: bool,
2688 pub is_list: bool,
2690}
2691
2692#[derive(Debug, Clone, PartialEq, Eq)]
2694#[non_exhaustive]
2695pub struct VariableBinding {
2696 pub sigil: String,
2698 pub name: String,
2700 pub range: SourceLocation,
2702}
2703
2704#[derive(Debug, Clone, PartialEq, Eq)]
2706#[non_exhaustive]
2707pub struct CallExpr {
2708 pub name: String,
2710 pub arg_count: usize,
2712 pub form: CallForm,
2714}
2715
2716#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
2718#[non_exhaustive]
2719pub enum CallForm {
2720 NamedFunction,
2722 Coderef,
2724}
2725
2726#[derive(Debug, Clone, PartialEq, Eq)]
2728#[non_exhaustive]
2729pub struct MethodCallExpr {
2730 pub method: String,
2732 pub arg_count: usize,
2734 pub object_kind: &'static str,
2736}
2737
2738#[derive(Debug, Clone, PartialEq, Eq)]
2740#[non_exhaustive]
2741pub struct IndirectCallExpr {
2742 pub method: String,
2744 pub arg_count: usize,
2746 pub object_kind: &'static str,
2748}
2749
2750#[derive(Debug, Clone, PartialEq, Eq)]
2752#[non_exhaustive]
2753pub struct BarewordExpr {
2754 pub name: String,
2756}
2757
2758#[derive(Debug, Clone, PartialEq, Eq)]
2760#[non_exhaustive]
2761pub struct LiteralExpr {
2762 pub kind: LiteralKind,
2764 pub value: Option<String>,
2766 pub interpolated: Option<bool>,
2768 pub element_count: Option<usize>,
2770 pub pair_count: Option<usize>,
2772}
2773
2774#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
2776#[non_exhaustive]
2777pub enum LiteralKind {
2778 Number,
2780 String,
2782 Undef,
2784 Array,
2786 Hash,
2788}
2789
2790#[derive(Debug, Clone, PartialEq, Eq)]
2792#[non_exhaustive]
2793pub struct BlockShell {
2794 pub statement_count: usize,
2796}
2797
2798#[derive(Debug, Clone, PartialEq, Eq)]
2800#[non_exhaustive]
2801pub struct DynamicBoundary {
2802 pub kind: DynamicBoundaryKind,
2804 pub reason: String,
2806}
2807
2808#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
2810#[non_exhaustive]
2811pub enum DynamicBoundaryKind {
2812 CoderefCall,
2814 EvalExpression,
2816 DoExpression,
2818 DynamicStashMutation,
2820 Autoload,
2822 SymbolicReferenceDeref,
2824}