use crate::core::backend::GeneratedFile;
use crate::core::config::{AdapterPattern, ResolvedCrateConfig};
use std::path::Path;
pub(crate) fn collect_already_declared_owner_types(config: &ResolvedCrateConfig) -> std::collections::BTreeSet<String> {
let mut owner_types = std::collections::BTreeSet::new();
for adapter in &config.adapters {
if matches!(adapter.pattern, AdapterPattern::Streaming) {
if let Some(owner) = adapter.owner_type.as_deref() {
owner_types.insert(owner.to_string());
}
}
}
owner_types
}
pub(crate) fn emit_opaque_class_declarations(
config: &ResolvedCrateConfig,
rust_bridge_sources: &Path,
) -> Option<GeneratedFile> {
let owner_types = collect_already_declared_owner_types(config);
if owner_types.is_empty() {
return None;
}
let mut content = String::new();
content.push_str("// Generated by alef — intentionally empty.\n");
content.push_str("// Streaming-adapter owner handles are declared canonically (once) in the\n");
content.push_str("// Rust extern blocks, so swift-bridge synthesizes their class triple and\n");
content.push_str("// `$_free` destructor directly; no hand-rolled class hierarchy is needed.\n\n");
content.push_str("import Foundation\n");
content.push_str("import RustBridgeC\n");
let path = rust_bridge_sources.join("RustBridgeOpaqueHandles.swift");
Some(GeneratedFile {
path,
content,
generated_header: false,
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::config::{AdapterConfig, AdapterParam, AdapterPattern};
fn streaming_adapter_with_owner(name: &str, owner: &str) -> AdapterConfig {
AdapterConfig {
name: name.to_string(),
pattern: AdapterPattern::Streaming,
core_path: format!("sample::{name}"),
params: vec![AdapterParam {
name: "req".to_string(),
ty: "sample::StreamRequest".to_string(),
optional: false,
}],
returns: None,
error_type: Some("String".to_string()),
owner_type: Some(owner.to_string()),
item_type: Some("StreamItem".to_string()),
gil_release: false,
trait_name: None,
trait_method: None,
detect_async: false,
request_type: Some("sample::StreamRequest".to_string()),
skip_languages: vec![],
}
}
#[test]
fn collect_already_declared_owner_types_deduplicates() {
let config = ResolvedCrateConfig {
adapters: vec![
streaming_adapter_with_owner("chat_stream", "DefaultClient"),
streaming_adapter_with_owner("batch_stream", "DefaultClient"),
streaming_adapter_with_owner("crawl_stream", "CrawlEngine"),
],
..Default::default()
};
let owners = collect_already_declared_owner_types(&config);
assert_eq!(owners.len(), 2);
assert!(owners.contains("DefaultClient"));
assert!(owners.contains("CrawlEngine"));
}
#[test]
fn collect_already_declared_owner_types_skips_non_streaming() {
let config = ResolvedCrateConfig {
adapters: vec![streaming_adapter_with_owner("stream", "StreamClient")],
..Default::default()
};
let owners = collect_already_declared_owner_types(&config);
assert_eq!(owners.len(), 1);
assert!(owners.contains("StreamClient"));
}
}