use crate::ir::abi::{AbiCall, CallId};
use crate::ir::definitions::{ConstructorDef, FieldDef, MethodDef, Receiver, RecordDef};
use crate::ir::ids::{FieldName, RecordId};
use crate::ir::ops::{ReadOp, ReadSeq, WriteOp, WriteSeq};
use super::super::ast::{
CSharpClassName, CSharpComment, CSharpExpression, CSharpIdentity, CSharpLocalName,
CSharpMethodName, CSharpType,
};
use super::super::plan::{
CSharpFieldPlan, CSharpMethodPlan, CSharpParamPlan, CSharpReceiver, CSharpRecordPlan,
CSharpReturnKind,
};
use super::lowerer::CSharpLowerer;
use super::wire_writers::self_wire_writer;
use super::{decode, encode, size, value};
impl<'a> CSharpLowerer<'a> {
pub(super) fn lower_record(&self, record: &RecordDef) -> CSharpRecordPlan {
let class_name: CSharpClassName = (&record.id).into();
let mut size_locals = size::SizeLocalCounters::default();
let mut encode_locals = encode::EncodeLocalCounters::default();
let mut decode_locals = decode::DecodeLocalCounters::default();
let fields = record
.fields
.iter()
.map(|field| {
self.lower_record_field(
&record.id,
field,
&mut size_locals,
&mut encode_locals,
&mut decode_locals,
)
})
.collect();
let is_blittable = self.is_blittable_record(&record.id);
let methods = self.lower_record_methods(record, &class_name, is_blittable);
CSharpRecordPlan {
summary_doc: CSharpComment::from_str_option(record.doc.as_deref()),
class_name,
fields,
is_blittable,
methods,
is_error: record.is_error,
}
}
fn lower_record_methods(
&self,
record: &RecordDef,
record_class_name: &CSharpClassName,
owner_is_blittable: bool,
) -> Vec<CSharpMethodPlan> {
let mut methods = Vec::new();
for (index, ctor) in record.constructors.iter().enumerate() {
if ctor.is_fallible() || ctor.is_optional() {
continue;
}
let call_id = CallId::RecordConstructor {
record_id: record.id.clone(),
index,
};
let Some(call) = self.abi.calls.iter().find(|c| c.id == call_id) else {
continue;
};
if let Some(method) =
self.lower_record_constructor(ctor, call, record_class_name, owner_is_blittable)
{
methods.push(method);
}
}
for method_def in &record.methods {
if method_def.is_async() {
continue;
}
if matches!(
method_def.receiver,
Receiver::RefMutSelf | Receiver::OwnedSelf
) {
continue;
}
let call_id = CallId::RecordMethod {
record_id: record.id.clone(),
method_id: method_def.id.clone(),
};
let Some(call) = self.abi.calls.iter().find(|c| c.id == call_id) else {
continue;
};
if let Some(method) =
self.lower_record_method(method_def, call, record_class_name, owner_is_blittable)
{
methods.push(method);
}
}
methods
}
fn lower_record_constructor(
&self,
ctor: &ConstructorDef,
call: &AbiCall,
record_class_name: &CSharpClassName,
owner_is_blittable: bool,
) -> Option<CSharpMethodPlan> {
let raw_name: &str = match ctor.name() {
Some(id) => id.as_str(),
None => "new",
};
let name = CSharpMethodName::from_source(raw_name);
let return_type = CSharpType::Record(record_class_name.clone().into());
let return_kind = if owner_is_blittable {
CSharpReturnKind::Direct
} else {
CSharpReturnKind::WireDecodeObject {
class_name: record_class_name.clone(),
}
};
let mut ctor_size_locals = size::SizeLocalCounters::default();
let mut ctor_encode_locals = encode::EncodeLocalCounters::default();
let wire_writers: Vec<_> = call
.params
.iter()
.filter_map(|p| {
self.wire_writer_for_param(p, &mut ctor_size_locals, &mut ctor_encode_locals)
})
.collect();
let param_defs = ctor.params();
let params: Vec<CSharpParamPlan> = param_defs
.iter()
.map(|p| self.lower_param(p, &wire_writers))
.collect::<Option<Vec<_>>>()?;
Some(CSharpMethodPlan {
summary_doc: CSharpComment::from_str_option(ctor.doc()),
native_method_name: CSharpMethodName::native_for_owner(record_class_name, &name),
name,
ffi_name: (&call.symbol).into(),
async_call: None,
receiver: CSharpReceiver::Static,
params,
return_type,
return_kind,
wire_writers,
owner_is_blittable: false,
})
}
fn lower_record_method(
&self,
method_def: &MethodDef,
call: &AbiCall,
record_class_name: &CSharpClassName,
owner_is_blittable: bool,
) -> Option<CSharpMethodPlan> {
let name: CSharpMethodName = (&method_def.id).into();
let return_type = self.lower_return(&method_def.returns)?;
let return_kind = self.return_kind(
&method_def.returns,
&return_type,
call.returns.decode_ops.as_ref(),
None,
);
let receiver = match method_def.receiver {
Receiver::Static => CSharpReceiver::Static,
Receiver::RefSelf | Receiver::RefMutSelf | Receiver::OwnedSelf => {
CSharpReceiver::InstanceNative
}
};
let explicit_abi_params = if matches!(receiver, CSharpReceiver::Static) {
&call.params[..]
} else {
&call.params[1..]
};
let mut method_size_locals = size::SizeLocalCounters::default();
let mut method_encode_locals = encode::EncodeLocalCounters::default();
let mut wire_writers: Vec<_> = Vec::new();
if matches!(receiver, CSharpReceiver::InstanceNative) && !owner_is_blittable {
wire_writers.push(self_wire_writer());
}
wire_writers.extend(explicit_abi_params.iter().filter_map(|p| {
self.wire_writer_for_param(p, &mut method_size_locals, &mut method_encode_locals)
}));
let params: Vec<CSharpParamPlan> = method_def
.params
.iter()
.map(|p| self.lower_param(p, &wire_writers))
.collect::<Option<Vec<_>>>()?;
let owner_is_blittable_for_call =
matches!(receiver, CSharpReceiver::InstanceNative) && owner_is_blittable;
Some(CSharpMethodPlan {
summary_doc: CSharpComment::from_str_option(method_def.doc.as_deref()),
native_method_name: CSharpMethodName::native_for_owner(record_class_name, &name),
name,
ffi_name: (&call.symbol).into(),
async_call: None,
receiver,
params,
return_type,
return_kind,
wire_writers,
owner_is_blittable: owner_is_blittable_for_call,
})
}
fn lower_record_field(
&self,
record_id: &RecordId,
field: &FieldDef,
size_locals: &mut size::SizeLocalCounters,
encode_locals: &mut encode::EncodeLocalCounters,
decode_locals: &mut decode::DecodeLocalCounters,
) -> CSharpFieldPlan {
let decode_seq = self
.record_field_read_seq(record_id, &field.name)
.expect("record field decode ops");
let encode_seq = self
.record_field_write_seq(record_id, &field.name)
.expect("record field encode ops");
let csharp_type = self
.lower_type(&field.type_expr)
.expect("record field type must be supported");
CSharpFieldPlan {
summary_doc: CSharpComment::from_str_option(field.doc.as_deref()),
name: (&field.name).into(),
csharp_type,
wire_decode_expr: decode::lower_decode_expr(
&decode_seq,
&CSharpExpression::Identity(CSharpIdentity::Local(CSharpLocalName::new("reader"))),
None,
&self.namespace,
decode_locals,
),
wire_size_expr: size::lower_size_expr(
&encode_seq.size,
&value::Renames::new(),
size_locals,
),
wire_encode_stmts: encode::lower_encode_expr(
&encode_seq,
&CSharpExpression::Identity(CSharpIdentity::Local(CSharpLocalName::new("wire"))),
&value::Renames::new(),
encode_locals,
),
}
}
fn record_field_read_seq(
&self,
record_id: &RecordId,
field_name: &FieldName,
) -> Option<ReadSeq> {
self.abi_record_for(record_id)
.and_then(|record| match record.decode_ops.ops.first() {
Some(ReadOp::Record { fields, .. }) => fields
.iter()
.find(|field| field.name == *field_name)
.map(|field| field.seq.clone()),
_ => None,
})
}
fn record_field_write_seq(
&self,
record_id: &RecordId,
field_name: &FieldName,
) -> Option<WriteSeq> {
self.abi_record_for(record_id)
.and_then(|record| match record.encode_ops.ops.first() {
Some(WriteOp::Record { fields, .. }) => fields
.iter()
.find(|field| field.name == *field_name)
.map(|field| field.seq.clone()),
_ => None,
})
}
}