use super::super::VersioningError;
use super::super::lifecycle::{Availability, Site, SiteKind, availability};
use super::super::paths::{declarators_name, join};
use super::super::timeline::{VersionDef, VersionId, VersionTimeline};
use super::declarations::for_each_declaration;
use super::references::{Reference, touch_const_expr, touch_const_type, touch_type};
use super::strip_annotations;
use crate::error::ParserResult;
use crate::hir::{
Annotation, AttrDclInner, AttrDeclarator, AttrRaisesExpr, ConstDcl, Definition, ExceptDcl,
Export, InterfaceDclInner, ModuleDcl, ReadonlyAttrDeclarator, ScopedName, TypeDcl, TypeSpec,
TypedefDcl, TypedefType,
};
use std::collections::BTreeSet;
pub(super) struct Projector<'a> {
pub(super) timeline: &'a VersionTimeline,
pub(super) version: VersionId,
pub(super) def: &'a VersionDef,
kept_paths: BTreeSet<String>,
dropped_paths: BTreeSet<String>,
pub(super) references: Vec<Reference>,
}
impl<'a> Projector<'a> {
pub(super) fn new(
timeline: &'a VersionTimeline,
version: VersionId,
def: &'a VersionDef,
) -> Self {
Self {
timeline,
version,
def,
kept_paths: BTreeSet::new(),
dropped_paths: BTreeSet::new(),
references: Vec::new(),
}
}
pub(super) fn prune_raises(&self, definitions: &mut [Definition]) {
for definition in definitions {
self.prune_definition(definition);
}
}
fn prune_definition(&self, definition: &mut Definition) {
match definition {
Definition::ModuleDcl(module) => self.prune_raises(&mut module.definition),
Definition::InterfaceDcl(interface) => {
let InterfaceDclInner::InterfaceDef(def) = &mut interface.decl else {
return;
};
let Some(body) = &mut def.interface_body else {
return;
};
for export in &mut body.0 {
self.prune_export(export);
}
}
_ => {}
}
}
fn prune_export(&self, export: &mut Export) {
match export {
Export::OpDcl(operation) => {
if let Some(raises) = operation.raises.as_mut() {
self.prune_names(&mut raises.0);
if raises.0.is_empty() {
operation.raises = None;
}
}
}
Export::AttrDcl(attribute) => match &mut attribute.decl {
AttrDclInner::ReadonlyAttrSpec(spec) => {
if let ReadonlyAttrDeclarator::WithRaises { declarator, raises } =
&mut spec.declarator
{
self.prune_names(&mut raises.0);
if raises.0.is_empty() {
spec.declarator =
ReadonlyAttrDeclarator::SimpleDeclarator(vec![declarator.clone()]);
}
}
}
AttrDclInner::AttrSpec(spec) => {
if let AttrDeclarator::WithRaises { declarator, raises } = &mut spec.declarator
{
match raises {
AttrRaisesExpr::Case1(get, set) => {
self.prune_names(&mut get.expr.0);
if let Some(set) = set {
self.prune_names(&mut set.expr.0);
}
match (get.expr.0.is_empty(), set.as_ref()) {
(true, Some(set)) if !set.expr.0.is_empty() => {
*raises = AttrRaisesExpr::SetExcepExpr(set.clone());
}
(false, Some(set)) if set.expr.0.is_empty() => {
*raises = AttrRaisesExpr::Case1(get.clone(), None);
}
(true, _) => {
spec.declarator = AttrDeclarator::SimpleDeclarator(vec![
declarator.clone(),
]);
}
_ => {}
}
}
AttrRaisesExpr::SetExcepExpr(set) => {
self.prune_names(&mut set.expr.0);
if set.expr.0.is_empty() {
spec.declarator =
AttrDeclarator::SimpleDeclarator(vec![declarator.clone()]);
}
}
}
}
}
},
_ => {}
}
}
fn prune_names(&self, names: &mut Vec<ScopedName>) {
names.retain(|name| !self.reference_unavailable(&name.name));
}
pub(super) fn check_references(&self) -> ParserResult<()> {
for reference in &self.references {
if self.reference_unavailable(&reference.segments) {
return Err(VersioningError::DanglingReference {
decl: reference.decl.clone(),
reference: reference.rendered(),
version: self.def.ident.clone(),
}
.into());
}
}
Ok(())
}
fn reference_unavailable(&self, segments: &[String]) -> bool {
if segments.is_empty() {
return false;
}
let target = segments.join("::");
let matches = |path: &String| path == &target || path.ends_with(&format!("::{target}"));
if self.kept_paths.iter().any(matches) {
return false;
}
self.dropped_paths.iter().any(matches)
}
pub(super) fn note_declaration(&mut self, path: &str, is_kept: bool) {
if path.is_empty() {
return;
}
if is_kept {
self.kept_paths.insert(path.to_string());
} else {
self.dropped_paths.insert(path.to_string());
}
}
pub(super) fn note_dropped_subtree(&mut self, definition: &Definition, path: &str) {
let mut paths = Vec::new();
for_each_declaration(
std::slice::from_ref(definition),
path,
&mut |declared: &str| {
paths.push(declared.to_string());
},
);
for declared in paths {
self.note_declaration(&declared, false);
}
}
pub(super) fn site(
&self,
path: &str,
parent_path: &str,
kind: SiteKind,
annotations: &'a [Annotation],
) -> Site<'a> {
Site {
path: path.to_string(),
parent_path: parent_path.to_string(),
kind,
annotations,
enum_members: None,
parameter: None,
}
}
pub(super) fn definition(
&mut self,
definition: &Definition,
parent: Availability,
path: &str,
) -> ParserResult<Option<Definition>> {
match definition {
Definition::Pragma(pragma) => Ok(Some(Definition::Pragma(pragma.clone()))),
Definition::ModuleDcl(module) => self.module(module, parent, path),
Definition::TypeDcl(type_dcl) => Ok(self
.type_dcl(type_dcl, parent, path)?
.map(Definition::TypeDcl)),
Definition::ConstrTypeDcl(constr) => Ok(self
.constr(constr, parent, path)?
.map(Definition::ConstrTypeDcl)),
Definition::ConstDcl(const_dcl) => Ok(self
.const_dcl(const_dcl, parent, path)?
.map(Definition::ConstDcl)),
Definition::ExceptDcl(except) => Ok(self
.except_dcl(except, parent, path)?
.map(Definition::ExceptDcl)),
Definition::InterfaceDcl(interface) => Ok(self
.interface_dcl(interface, parent, path)?
.map(Definition::InterfaceDcl)),
}
}
fn module(
&mut self,
module: &ModuleDcl,
parent: Availability,
path: &str,
) -> ParserResult<Option<Definition>> {
let here = join(path, &module.ident);
let site = self.site(&here, path, SiteKind::Module, &module.annotations);
let own = availability(&site, parent, self.timeline)?;
if !own.contains(self.version) {
self.note_dropped_subtree(&Definition::ModuleDcl(module.clone()), path);
return Ok(None);
}
let mut definitions = Vec::new();
for definition in &module.definition {
if let Some(projected) = self.definition(definition, own, &here)? {
definitions.push(projected);
}
}
Ok(Some(Definition::ModuleDcl(ModuleDcl {
annotations: strip_annotations(&module.annotations),
ident: module.ident.clone(),
definition: definitions,
})))
}
pub(super) fn type_dcl(
&mut self,
type_dcl: &TypeDcl,
parent: Availability,
path: &str,
) -> ParserResult<Option<TypeDcl>> {
match type_dcl {
TypeDcl::ConstrTypeDcl(constr) => Ok(self
.constr(constr, parent, path)?
.map(TypeDcl::ConstrTypeDcl)),
TypeDcl::TypedefDcl(typedef) => self.typedef_dcl(typedef, parent, path),
TypeDcl::NativeDcl(native) => {
let here = join(path, &native.decl.0);
let site = self.site(&here, path, SiteKind::Type, &native.annotations);
let own = availability(&site, parent, self.timeline)?;
self.note_declaration(&here, own.contains(self.version));
if !own.contains(self.version) {
return Ok(None);
}
let mut projected = native.clone();
projected.annotations = strip_annotations(&native.annotations);
Ok(Some(TypeDcl::NativeDcl(projected)))
}
}
}
fn typedef_dcl(
&mut self,
typedef: &TypedefDcl,
parent: Availability,
path: &str,
) -> ParserResult<Option<TypeDcl>> {
let names = declarators_name(&typedef.decl);
let here = join(path, &names);
let site = self.site(&here, path, SiteKind::Type, &typedef.annotations);
let own = availability(&site, parent, self.timeline)?;
for declarator in &typedef.decl {
self.note_declaration(
&join(path, &super::super::paths::declarator_name(declarator)),
own.contains(self.version),
);
}
if !own.contains(self.version) {
return Ok(None);
}
let mut projected = typedef.clone();
projected.annotations = strip_annotations(&typedef.annotations);
super::references::touch_declarators(&mut projected.decl, &mut self.references, &here);
match &typedef.ty {
TypedefType::TypeSpec(ty) => {
let mut ty = ty.clone();
self.touch_type(&mut ty, &here);
projected.ty = TypedefType::TypeSpec(ty);
}
TypedefType::ConstrTypeDcl(constr) => {
projected.ty = match self.constr(constr, own, &here)? {
Some(nested) => TypedefType::ConstrTypeDcl(nested),
None => return Ok(None),
};
}
}
Ok(Some(TypeDcl::TypedefDcl(projected)))
}
pub(super) fn const_dcl(
&mut self,
const_dcl: &ConstDcl,
parent: Availability,
path: &str,
) -> ParserResult<Option<ConstDcl>> {
let here = join(path, &const_dcl.ident);
let site = self.site(&here, path, SiteKind::Unsupported, &const_dcl.annotations);
let own = availability(&site, parent, self.timeline)?;
self.note_declaration(&here, own.contains(self.version));
if !own.contains(self.version) {
return Ok(None);
}
let mut projected = const_dcl.clone();
projected.annotations = strip_annotations(&const_dcl.annotations);
touch_const_type(&mut projected.ty, &mut self.references, &here);
touch_const_expr(&mut projected.value, &mut self.references, &here);
Ok(Some(projected))
}
pub(super) fn except_dcl(
&mut self,
except: &ExceptDcl,
parent: Availability,
path: &str,
) -> ParserResult<Option<ExceptDcl>> {
let here = join(path, &except.ident);
let site = self.site(&here, path, SiteKind::Type, &except.annotations);
let own = availability(&site, parent, self.timeline)?;
self.note_declaration(&here, own.contains(self.version));
if !own.contains(self.version) {
return Ok(None);
}
let mut projected = except.clone();
projected.annotations = strip_annotations(&except.annotations);
projected.member = self.members(&except.member, own, &here)?;
Ok(Some(projected))
}
pub(super) fn touch_type(&mut self, ty: &mut TypeSpec, decl: &str) {
touch_type(ty, &mut self.references, decl);
}
}