use pedant_types::{StructureRecord, StructureSpan};
use crate::ir::FileIr;
use crate::ir::extract::{self, ParseCompatibility};
use crate::ir::facts::IrSpan;
use crate::ir::sites::{IrRange, StructureSite};
use crate::resolution::line_index::LineIndex;
use super::depth::syntax_depth;
use super::fault::RustSourceFault;
use super::limits::admits_depth;
#[derive(Debug)]
pub struct RustFileInventory {
ir: FileIr,
structures: Box<[StructureRecord]>,
definitions: Box<[Option<StructureSpan>]>,
syntax_depth: u32,
compatibility: ParseCompatibility,
}
impl RustFileInventory {
pub fn of_source(
path: &str,
text: &str,
max_syntax_depth: u32,
) -> Result<Self, RustSourceFault> {
let depth = syntax_depth(text).map_err(|error| unparsed(&error))?;
within_depth(depth, max_syntax_depth)?;
let parsed = extract::parse_source_for_edition(path, text, true)
.map_err(|error| unparsed(&error))?;
let ir = extract::extract(path, &parsed.file, None);
let (structures, definitions) = retained_structures(&ir, text);
Ok(Self {
structures,
definitions,
ir,
syntax_depth: depth,
compatibility: parsed.compatibility,
})
}
pub fn ir(&self) -> &FileIr {
&self.ir
}
pub fn structures(&self) -> &[StructureRecord] {
&self.structures
}
pub fn definition_span(&self, position: usize) -> Option<StructureSpan> {
self.definitions.get(position).copied().flatten()
}
pub fn syntax_depth(&self) -> u32 {
self.syntax_depth
}
pub fn legacy_callable_trait_error(&self) -> Option<&str> {
match &self.compatibility {
ParseCompatibility::AllEditions => None,
ParseCompatibility::LegacyCallableTraits { strict_error } => Some(strict_error),
}
}
}
fn within_depth(depth: u32, ceiling: u32) -> Result<(), RustSourceFault> {
match admits_depth(depth, ceiling) {
true => Ok(()),
false => Err(RustSourceFault::SyntaxDepth {
ceiling: ceiling.into(),
}),
}
}
fn retained_structures(
ir: &FileIr,
text: &str,
) -> (Box<[StructureRecord]>, Box<[Option<StructureSpan>]>) {
let index = LineIndex::new(text);
let (structures, definitions): (Vec<StructureRecord>, Vec<Option<StructureSpan>>) = ir
.structure_sites
.iter()
.map(|site| retained_structure(ir, site, &index))
.unzip();
(
structures.into_boxed_slice(),
definitions.into_boxed_slice(),
)
}
fn retained_structure(
ir: &FileIr,
site: &StructureSite,
index: &LineIndex<'_>,
) -> (StructureRecord, Option<StructureSpan>) {
let named = site
.definition
.and_then(|definition| ir.definition_sites.get(definition.index()));
let record = StructureRecord::new(
site.kind,
named.map(|named| Box::from(&*named.name)),
span_of(index, site.range),
site.parent
.and_then(|owner| u32::try_from(owner.index()).ok()),
);
(record, named.map(|named| span_of(index, named.range)))
}
pub(super) fn span_of(index: &LineIndex<'_>, range: IrRange) -> StructureSpan {
let start = offset_of(index, range.start);
let end = offset_of(index, range.end).max(start);
StructureSpan::new(
start..end,
line_number(range.start.line),
line_number(range.end.line),
)
}
fn line_number(line: usize) -> u32 {
u32::try_from(line).unwrap_or(u32::MAX)
}
pub(super) fn offset_of(index: &LineIndex<'_>, at: IrSpan) -> usize {
let Some(line) = at.line.checked_sub(1) else {
return 0;
};
index
.char_offset(line, at.column)
.unwrap_or_else(|| index.source().len())
}
fn unparsed<E: std::fmt::Display>(error: &E) -> RustSourceFault {
RustSourceFault::Unparsed {
reason: error.to_string().into_boxed_str(),
}
}