use alloc::vec::Vec;
use core::fmt;
use zerodds_idl::ast::{Export, InterfaceDef, ScopedName};
use crate::transform::ComponentEquivalent;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum LightweightFilterError {
EmptyAfterFilter,
}
impl fmt::Display for LightweightFilterError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::EmptyAfterFilter => f.write_str(
"component would have no operations after applying lightweight CCM filter",
),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for LightweightFilterError {}
pub fn filter_to_lightweight(
eq: ComponentEquivalent,
) -> Result<ComponentEquivalent, LightweightFilterError> {
let kept_exports: Vec<Export> = eq
.equivalent_interface
.exports
.into_iter()
.filter(|e| !is_filtered_export(e))
.collect();
if kept_exports.is_empty() && !eq.event_consumer_interfaces.is_empty() {
return Err(LightweightFilterError::EmptyAfterFilter);
}
let filtered_iface = InterfaceDef {
exports: kept_exports,
bases: eq
.equivalent_interface
.bases
.into_iter()
.filter(|b| !is_filtered_base(b))
.collect(),
..eq.equivalent_interface
};
Ok(ComponentEquivalent {
equivalent_interface: filtered_iface,
event_consumer_interfaces: eq.event_consumer_interfaces,
})
}
fn is_filtered_export(e: &Export) -> bool {
if let Export::Op(o) = e {
let n = &o.name.text;
matches!(
n.as_str(),
"configure" | "set_configuration" | "configuration_complete"
)
} else {
false
}
}
fn is_filtered_base(b: &ScopedName) -> bool {
let _ = b;
false
}
#[cfg(test)]
#[allow(clippy::expect_used)]
mod tests {
use super::*;
use crate::transform::transform_component;
use zerodds_idl::ast::{
ComponentDef, ComponentExport, Identifier, OpDecl, ParamAttribute, ParamDecl,
PrimitiveType, ScopedName, TypeSpec,
};
use zerodds_idl::errors::Span;
fn span() -> Span {
Span::SYNTHETIC
}
fn ident(s: &str) -> Identifier {
Identifier::new(s, span())
}
fn sn(parts: &[&str]) -> ScopedName {
ScopedName {
absolute: false,
parts: parts.iter().map(|p| ident(p)).collect(),
span: span(),
}
}
#[test]
fn lightweight_filter_drops_configurator_operations() {
let c = ComponentDef {
name: ident("C"),
base: None,
supports: Vec::new(),
body: alloc::vec![ComponentExport::Provides {
type_spec: sn(&["I"]),
name: ident("foo"),
span: span(),
}],
annotations: Vec::new(),
span: span(),
};
let mut eq = transform_component(&c);
eq.equivalent_interface.exports.push(Export::Op(OpDecl {
name: ident("configure"),
oneway: false,
return_type: None,
params: alloc::vec![ParamDecl {
attribute: ParamAttribute::In,
type_spec: TypeSpec::Primitive(PrimitiveType::Boolean),
name: ident("comp"),
annotations: Vec::new(),
span: span(),
}],
raises: Vec::new(),
annotations: Vec::new(),
span: span(),
}));
let filtered = filter_to_lightweight(eq).expect("filter ok");
let names: Vec<String> = filtered
.equivalent_interface
.exports
.iter()
.filter_map(|e| match e {
Export::Op(o) => Some(o.name.text.clone()),
_ => None,
})
.collect();
assert!(names.contains(&String::from("provide_foo")));
assert!(!names.contains(&String::from("configure")));
}
#[test]
fn lightweight_filter_keeps_typespecific_ops() {
let c = ComponentDef {
name: ident("C"),
base: None,
supports: Vec::new(),
body: alloc::vec![
ComponentExport::Provides {
type_spec: sn(&["I"]),
name: ident("foo"),
span: span(),
},
ComponentExport::Uses {
type_spec: sn(&["J"]),
name: ident("bar"),
multiple: false,
span: span(),
},
],
annotations: Vec::new(),
span: span(),
};
let eq = transform_component(&c);
let filtered = filter_to_lightweight(eq).expect("filter ok");
let names: Vec<String> = filtered
.equivalent_interface
.exports
.iter()
.filter_map(|e| match e {
Export::Op(o) => Some(o.name.text.clone()),
_ => None,
})
.collect();
for expected in [
"provide_foo",
"connect_bar",
"disconnect_bar",
"get_connection_bar",
] {
assert!(
names.contains(&String::from(expected)),
"missing {expected}"
);
}
}
#[test]
fn display_error_describes_empty_after_filter() {
let s = alloc::format!("{}", LightweightFilterError::EmptyAfterFilter);
assert!(s.contains("lightweight"));
}
}