use crate::core::config::ResolvedCrateConfig;
use crate::core::ir::{ErrorDef, MethodDef, TypeDef};
use crate::e2e::fixture::{CallbackAction, Fixture};
pub(super) type CallbackProbe<'a> = (&'a str, &'a CallbackAction, Option<VisitorContextProbe>);
pub(super) fn visitor_callback_probes<'a>(
config: &ResolvedCrateConfig,
type_defs: &[TypeDef],
errors: &[ErrorDef],
convertible_types: &ahash::AHashSet<String>,
fixture: &'a Fixture,
) -> Vec<CallbackProbe<'a>> {
fixture
.visitor
.iter()
.flat_map(|visitor_spec| visitor_spec.callbacks.iter())
.map(|(method_name, action)| {
(
method_name.as_str(),
action,
visitor_context_probe(config, type_defs, errors, convertible_types, method_name),
)
})
.collect()
}
pub(super) fn distinct_context_probes<'a>(callbacks: &'a [CallbackProbe<'a>]) -> Vec<&'a VisitorContextProbe> {
let mut distinct: Vec<&VisitorContextProbe> = Vec::new();
for probe in callbacks.iter().filter_map(|(_, _, probe)| probe.as_ref()) {
if !distinct.iter().any(|seen| seen.probe_method == probe.probe_method) {
distinct.push(probe);
}
}
distinct
}
#[derive(serde::Serialize)]
pub(super) struct VisitorContextProbe {
pub probe_method: String,
pub attributes: Vec<String>,
pub methods: Vec<String>,
}
impl VisitorContextProbe {
fn is_empty(&self) -> bool {
self.attributes.is_empty() && self.methods.is_empty()
}
}
pub(super) fn visitor_context_probe(
config: &ResolvedCrateConfig,
type_defs: &[TypeDef],
errors: &[ErrorDef],
convertible_types: &ahash::AHashSet<String>,
callback_name: &str,
) -> Option<VisitorContextProbe> {
let context_def = callback_context_type(config, type_defs, errors, convertible_types, callback_name)?;
let probe = VisitorContextProbe {
probe_method: format!("_probe_{}", crate::codegen::naming::to_python_name(&context_def.name)),
attributes: probed_attribute_names(config, context_def),
methods: probed_method_names(config, context_def),
};
(!probe.is_empty()).then_some(probe)
}
fn callback_context_type<'a>(
config: &ResolvedCrateConfig,
type_defs: &'a [TypeDef],
errors: &[ErrorDef],
convertible_types: &ahash::AHashSet<String>,
callback_name: &str,
) -> Option<&'a TypeDef> {
let pyclass_absent =
crate::backends::pyo3::gen_bindings::binding_exclusions::pyclass_absent_type_names(config, type_defs, errors);
config.trait_bridges.iter().find_map(|bridge| {
let trait_def = type_defs
.iter()
.find(|type_def| type_def.is_trait && type_def.name == bridge.trait_name)?;
trait_def.methods.iter().find(|method| method.name == callback_name)?;
let context_type = bridge.context_type.as_deref()?;
if pyclass_absent.contains(context_type) {
return None;
}
let context_def = type_defs.iter().find(|type_def| type_def.name == context_type)?;
eligible_context_def(context_def, convertible_types)
})
}
fn eligible_context_def<'a>(
context_def: &'a TypeDef,
convertible_types: &ahash::AHashSet<String>,
) -> Option<&'a TypeDef> {
if !context_def.is_clone {
return None;
}
crate::codegen::conversions::core_to_binding_from_impl_emitted(context_def, convertible_types)
.then_some(context_def)
}
fn probed_attribute_names(config: &ResolvedCrateConfig, context_def: &TypeDef) -> Vec<String> {
crate::codegen::shared::binding_fields(&context_def.fields)
.filter(|field| field.cfg.is_none())
.map(|field| crate::backends::pyo3::gen_bindings::python_visible_field_name(config, &context_def.name, field))
.collect()
}
fn probed_method_names(config: &ResolvedCrateConfig, context_def: &TypeDef) -> Vec<String> {
let field_names: std::collections::HashSet<String> = context_def
.fields
.iter()
.flat_map(|field| {
[
crate::backends::pyo3::gen_bindings::python_visible_field_name(config, &context_def.name, field),
field.name.clone(),
]
})
.collect();
context_def
.methods
.iter()
.filter(|method| is_probeable_method(method))
.filter(|method| !field_names.contains(&method.name))
.map(|method| method.name.clone())
.collect()
}
fn is_probeable_method(method: &MethodDef) -> bool {
!method.binding_excluded
&& !method.sanitized
&& !method.is_static
&& method.receiver.is_some()
&& method.cfg.is_none()
&& method.params.is_empty()
&& !method.is_async
&& method.error_type.is_none()
&& crate::core::keywords::python_safe_name(&method.name).is_none()
}
#[cfg(test)]
mod tests;