use boltffi_ffi_rules::naming;
use crate::ir::abi::{AbiCall, AbiStream, CallId, CallMode};
use crate::ir::definitions::{ClassDef, ConstructorDef, MethodDef, Receiver, StreamDef};
use crate::ir::plan::Transport;
use super::super::ast::{CSharpClassName, CSharpComment, CSharpMethodName};
use super::super::plan::{
CSharpClassPlan, CSharpConstructorKind, CSharpConstructorPlan, CSharpMethodPlan,
CSharpParamPlan, CSharpReceiver, CSharpStreamPlan,
};
use super::functions::csharp_async_call_plan;
use super::lowerer::CSharpLowerer;
use super::{encode, size};
impl<'a> CSharpLowerer<'a> {
pub(super) fn lower_class(&self, class: &ClassDef) -> CSharpClassPlan {
let class_name = CSharpClassName::from_source(class.id.as_str());
let ffi_free = naming::class_ffi_free(class.id.as_str()).into();
let native_free_method_name =
CSharpMethodName::native_for_owner(&class_name, &CSharpMethodName::new("Free"));
let constructors = self.lower_class_constructors(class, &class_name);
let methods = self.lower_class_methods(class, &class_name);
let streams = self.lower_class_streams(class, &class_name);
CSharpClassPlan {
summary_doc: CSharpComment::from_str_option(class.doc.as_deref()),
class_name,
ffi_free,
native_free_method_name,
constructors,
methods,
streams,
}
}
fn lower_class_constructors(
&self,
class: &ClassDef,
class_name: &CSharpClassName,
) -> Vec<CSharpConstructorPlan> {
class
.constructors
.iter()
.enumerate()
.filter(|(_, ctor)| !ctor.is_fallible() && !ctor.is_optional())
.filter_map(|(index, ctor)| {
let call = self.abi.calls.iter().find(|c| {
c.id == CallId::Constructor {
class_id: class.id.clone(),
index,
}
})?;
self.lower_class_constructor(ctor, call, class_name)
})
.collect()
}
fn lower_class_constructor(
&self,
ctor: &ConstructorDef,
call: &AbiCall,
class_name: &CSharpClassName,
) -> Option<CSharpConstructorPlan> {
let kind = match ctor {
ConstructorDef::Default { .. } => CSharpConstructorKind::Primary {
helper_method_name: CSharpMethodName::new(format!("{class_name}NewHandle")),
},
ConstructorDef::NamedFactory { name, .. } | ConstructorDef::NamedInit { name, .. } => {
CSharpConstructorKind::StaticFactory {
name: CSharpMethodName::from_source(name.as_str()),
}
}
};
let surface_name = match &kind {
CSharpConstructorKind::Primary { .. } => CSharpMethodName::new("New"),
CSharpConstructorKind::StaticFactory { name } => name.clone(),
};
let native_method_name = CSharpMethodName::native_for_owner(class_name, &surface_name);
let mut size_locals = size::SizeLocalCounters::default();
let mut encode_locals = encode::EncodeLocalCounters::default();
let wire_writers: Vec<_> = call
.params
.iter()
.filter_map(|p| self.wire_writer_for_param(p, &mut size_locals, &mut encode_locals))
.collect();
let params: Vec<CSharpParamPlan> = ctor
.params()
.iter()
.map(|p| self.lower_param(p, &wire_writers))
.collect::<Option<_>>()?;
Some(CSharpConstructorPlan {
summary_doc: CSharpComment::from_str_option(ctor.doc()),
kind,
native_method_name,
ffi_name: (&call.symbol).into(),
params,
wire_writers,
})
}
fn lower_class_methods(
&self,
class: &ClassDef,
class_name: &CSharpClassName,
) -> Vec<CSharpMethodPlan> {
class
.methods
.iter()
.filter(|m| !matches!(m.receiver, Receiver::OwnedSelf))
.filter_map(|method_def| {
let call = self.abi.calls.iter().find(|c| {
c.id == CallId::Method {
class_id: class.id.clone(),
method_id: method_def.id.clone(),
}
})?;
self.lower_class_method(method_def, call, class_name)
})
.collect()
}
fn lower_class_method(
&self,
method_def: &MethodDef,
call: &AbiCall,
class_name: &CSharpClassName,
) -> Option<CSharpMethodPlan> {
let receiver = match method_def.receiver {
Receiver::Static => CSharpReceiver::Static,
Receiver::RefSelf | Receiver::RefMutSelf => CSharpReceiver::ClassInstance,
Receiver::OwnedSelf => return None,
};
let return_type = self.lower_return(&method_def.returns)?;
let complete_decode_ops = match &call.mode {
CallMode::Sync => call.returns.decode_ops.as_ref(),
CallMode::Async(async_call) => async_call.result.decode_ops.as_ref(),
};
let return_kind =
self.return_kind(&method_def.returns, &return_type, complete_decode_ops, None);
let explicit_abi_params = if matches!(receiver, CSharpReceiver::Static) {
&call.params[..]
} else {
&call.params[1..]
};
let mut size_locals = size::SizeLocalCounters::default();
let mut encode_locals = encode::EncodeLocalCounters::default();
let wire_writers: Vec<_> = explicit_abi_params
.iter()
.filter_map(|p| self.wire_writer_for_param(p, &mut size_locals, &mut encode_locals))
.collect();
let params: Vec<CSharpParamPlan> = method_def
.params
.iter()
.map(|p| self.lower_param(p, &wire_writers))
.collect::<Option<_>>()?;
let name: CSharpMethodName = (&method_def.id).into();
let native_method_name = CSharpMethodName::native_for_owner(class_name, &name);
let async_call = match &call.mode {
CallMode::Sync => None,
CallMode::Async(async_call) => {
Some(csharp_async_call_plan(async_call, &native_method_name))
}
};
Some(CSharpMethodPlan {
summary_doc: CSharpComment::from_str_option(method_def.doc.as_deref()),
native_method_name,
name,
ffi_name: (&call.symbol).into(),
async_call,
receiver,
params,
return_type,
return_kind,
wire_writers,
owner_is_blittable: false,
})
}
fn lower_class_streams(
&self,
class: &ClassDef,
class_name: &CSharpClassName,
) -> Vec<CSharpStreamPlan> {
class
.streams
.iter()
.filter_map(|stream_def| {
let abi_stream = self.abi_stream_for(&class.id, stream_def)?;
self.lower_class_stream(class.id.as_str(), stream_def, abi_stream, class_name)
})
.collect()
}
fn lower_class_stream(
&self,
class_id: &str,
stream_def: &StreamDef,
abi_stream: &AbiStream,
class_name: &CSharpClassName,
) -> Option<CSharpStreamPlan> {
let item_type = self.lower_type(&stream_def.item_type)?;
let name: CSharpMethodName = (&stream_def.id).into();
let subscribe_method_name = CSharpMethodName::native_for_owner(class_name, &name);
match &abi_stream.item_transport {
Transport::Scalar(_) | Transport::Composite(_) => {}
_ => panic!(
"C# stream over non-blittable item type for {}::{} is not supported yet",
class_id, stream_def.id
),
}
Some(CSharpStreamPlan {
summary_doc: CSharpComment::from_str_option(stream_def.doc.as_deref()),
name,
item_type,
mode: abi_stream.mode,
subscribe_method_name: subscribe_method_name.clone(),
subscribe_ffi_name: (&abi_stream.subscribe).into(),
pop_batch_method_name: CSharpMethodName::new(format!(
"{subscribe_method_name}PopBatch"
)),
pop_batch_ffi_name: (&abi_stream.pop_batch).into(),
wait_method_name: CSharpMethodName::new(format!("{subscribe_method_name}Wait")),
wait_ffi_name: (&abi_stream.wait).into(),
unsubscribe_method_name: CSharpMethodName::new(format!(
"{subscribe_method_name}Unsubscribe"
)),
unsubscribe_ffi_name: (&abi_stream.unsubscribe).into(),
free_method_name: CSharpMethodName::new(format!("{subscribe_method_name}Free")),
free_ffi_name: (&abi_stream.free).into(),
})
}
}