use crate::e2e::config::CallConfig;
#[derive(Clone, Copy, Default)]
pub(crate) struct CallIr<'a> {
pub functions: &'a [crate::core::ir::FunctionDef],
pub type_defs: &'a [crate::core::ir::TypeDef],
}
impl<'a> CallIr<'a> {
pub(crate) fn is_absent(self) -> bool {
self.functions.is_empty() && self.type_defs.is_empty()
}
pub(crate) fn signature(self, name: &str) -> Option<IrSignature<'a>> {
if let Some(function) = self.functions.iter().find(|function| function.name == name) {
return Some(IrSignature {
params: &function.params,
return_type: &function.return_type,
error_type: function.error_type.as_deref(),
is_async: function.is_async,
});
}
let mut methods = self
.type_defs
.iter()
.flat_map(|type_def| type_def.methods.iter())
.filter(|method| method.name == name);
let first = methods.next()?;
if !methods.all(|other| same_signature(first, other)) {
return None;
}
Some(IrSignature {
params: &first.params,
return_type: &first.return_type,
error_type: first.error_type.as_deref(),
is_async: first.is_async,
})
}
}
pub(crate) struct IrSignature<'a> {
pub params: &'a [crate::core::ir::ParamDef],
pub return_type: &'a crate::core::ir::TypeRef,
pub error_type: Option<&'a str>,
pub is_async: bool,
}
fn same_signature(left: &crate::core::ir::MethodDef, right: &crate::core::ir::MethodDef) -> bool {
left.return_type == right.return_type
&& left.params.len() == right.params.len()
&& left
.params
.iter()
.zip(right.params.iter())
.all(|(left, right)| left.name == right.name && left.ty == right.ty)
}
pub(crate) fn named_type(type_ref: &crate::core::ir::TypeRef) -> Option<&str> {
match type_ref {
crate::core::ir::TypeRef::Named(name) => Some(name),
crate::core::ir::TypeRef::Optional(inner) | crate::core::ir::TypeRef::Vec(inner) => named_type(inner),
_ => None,
}
}
pub(crate) fn resolve_declared_result_type(call: &CallConfig, lang: &str, ir: CallIr<'_>) -> Option<String> {
let lookup_name = call.core_lookup_name(lang)?;
let signature = ir.signature(&lookup_name)?;
named_type(signature.return_type).map(str::to_string)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ParamOptionalityRule {
DeclaredType,
Napi,
}
impl ParamOptionalityRule {
pub(crate) fn for_language(language: &str) -> Self {
match language {
"node" | "typescript" => Self::Napi,
_ => Self::DeclaredType,
}
}
pub(crate) fn is_optional(self, param: &crate::core::ir::ParamDef, type_defs: &[crate::core::ir::TypeDef]) -> bool {
match self {
Self::DeclaredType => param.optional,
Self::Napi => crate::backends::napi::napi_param_is_optional(param, type_defs),
}
}
}
#[derive(Clone, Copy)]
pub(crate) enum TargetParams<'a> {
Known(&'a [crate::core::ir::ParamDef]),
IrAbsent,
Unresolvable,
}
impl<'a> TargetParams<'a> {
pub(crate) fn resolve(call: &CallConfig, language: &str, ir: CallIr<'a>) -> Self {
if ir.is_absent() {
return Self::IrAbsent;
}
let lookup_name = call.core_lookup_name(language);
lookup_name
.as_deref()
.and_then(|name| ir.signature(name))
.map_or(Self::Unresolvable, |signature| Self::Known(signature.params))
}
pub(crate) fn known(self) -> Option<&'a [crate::core::ir::ParamDef]> {
match self {
Self::Known(params) => Some(params),
Self::IrAbsent | Self::Unresolvable => None,
}
}
pub(crate) fn param_for(self, arg_name: &str, index: usize) -> Option<&'a crate::core::ir::ParamDef> {
let params = self.known()?;
params
.iter()
.find(|param| param.name == arg_name)
.or_else(|| params.get(index))
}
pub(crate) fn declares_param_optional(
self,
language: &str,
arg_name: &str,
index: usize,
type_defs: &[crate::core::ir::TypeDef],
) -> Option<bool> {
let param = self.param_for(arg_name, index)?;
Some(ParamOptionalityRule::for_language(language).is_optional(param, type_defs))
}
pub(crate) fn declared_type_name(self, arg_name: &str, index: usize) -> Option<&'a str> {
named_type(&self.param_for(arg_name, index)?.ty)
}
}
#[cfg(test)]
mod tests {
use super::{CallIr, TargetParams, named_type};
use crate::core::ir::{FunctionDef, MethodDef, ParamDef, PrimitiveType, TypeDef, TypeRef};
use crate::e2e::config::CallConfig;
fn param(name: &str, ty: TypeRef) -> ParamDef {
ParamDef {
name: name.to_string(),
ty,
..ParamDef::default()
}
}
fn function(name: &str, params: Vec<ParamDef>) -> FunctionDef {
FunctionDef {
name: name.to_string(),
params,
return_type: TypeRef::Named("Response".to_string()),
..FunctionDef::default()
}
}
fn call_named(function: &str) -> CallConfig {
CallConfig {
function: function.to_string(),
..CallConfig::default()
}
}
#[test]
fn resolves_declared_params_for_a_free_function() {
let functions = vec![function(
"complete",
vec![param("request", TypeRef::Named("CompletionRequest".to_string()))],
)];
let ir = CallIr {
functions: &functions,
type_defs: &[],
};
let target = TargetParams::resolve(&call_named("complete"), "java", ir);
assert_eq!(target.declared_type_name("request", 0), Some("CompletionRequest"));
}
#[test]
fn resolves_through_a_per_language_function_override() {
let functions = vec![function(
"complete",
vec![param("request", TypeRef::Named("CompletionRequest".to_string()))],
)];
let ir = CallIr {
functions: &functions,
type_defs: &[],
};
let mut call = call_named("complete");
call.overrides.insert(
"java".to_string(),
crate::e2e::config::CallOverride {
function: Some("completeAsync".to_string()),
..crate::e2e::config::CallOverride::default()
},
);
let target = TargetParams::resolve(&call, "java", ir);
assert_eq!(target.declared_type_name("request", 0), Some("CompletionRequest"));
}
#[test]
fn resolves_a_method_declared_on_an_ir_type() {
let type_defs = vec![TypeDef {
name: "Client".to_string(),
methods: vec![MethodDef {
name: "chat".to_string(),
params: vec![param("request", TypeRef::Named("ChatRequest".to_string()))],
return_type: TypeRef::Named("ChatResponse".to_string()),
..MethodDef::default()
}],
..TypeDef::default()
}];
let ir = CallIr {
functions: &[],
type_defs: &type_defs,
};
let target = TargetParams::resolve(&call_named("chat"), "swift", ir);
assert_eq!(target.declared_type_name("request", 0), Some("ChatRequest"));
}
#[test]
fn an_absent_ir_is_ir_absent_and_licenses_no_type_claim() {
let target = TargetParams::resolve(&call_named("complete"), "java", CallIr::default());
assert!(matches!(target, TargetParams::IrAbsent));
assert_eq!(target.declared_type_name("request", 0), None);
assert!(target.known().is_none());
}
#[test]
fn a_present_ir_missing_the_call_is_unresolvable() {
let functions = vec![function("complete", vec![])];
let ir = CallIr {
functions: &functions,
type_defs: &[],
};
let target = TargetParams::resolve(&call_named("mystery"), "java", ir);
assert!(matches!(target, TargetParams::Unresolvable));
assert_eq!(target.declared_type_name("request", 0), None);
}
#[test]
fn disagreeing_same_named_methods_are_unresolvable() {
let type_defs = vec![
TypeDef {
name: "A".to_string(),
methods: vec![MethodDef {
name: "new".to_string(),
params: vec![param("value", TypeRef::Named("Alpha".to_string()))],
return_type: TypeRef::Named("A".to_string()),
..MethodDef::default()
}],
..TypeDef::default()
},
TypeDef {
name: "B".to_string(),
methods: vec![MethodDef {
name: "new".to_string(),
params: vec![param("value", TypeRef::Named("Beta".to_string()))],
return_type: TypeRef::Named("B".to_string()),
..MethodDef::default()
}],
..TypeDef::default()
},
];
let ir = CallIr {
functions: &[],
type_defs: &type_defs,
};
assert!(matches!(
TargetParams::resolve(&call_named("new"), "kotlin", ir),
TargetParams::Unresolvable
));
}
#[test]
fn a_zero_argument_target_is_known_and_empty() {
let functions = vec![function("ping", vec![])];
let ir = CallIr {
functions: &functions,
type_defs: &[],
};
let target = TargetParams::resolve(&call_named("ping"), "zig", ir);
assert_eq!(target.known().map(<[ParamDef]>::len), Some(0));
}
#[test]
fn matches_a_param_by_name_before_position() {
let functions = vec![function(
"complete",
vec![
param("first", TypeRef::Named("Alpha".to_string())),
param("second", TypeRef::Named("Beta".to_string())),
],
)];
let ir = CallIr {
functions: &functions,
type_defs: &[],
};
let target = TargetParams::resolve(&call_named("complete"), "csharp", ir);
assert_eq!(target.declared_type_name("second", 0), Some("Beta"));
assert_eq!(target.declared_type_name("unnamed", 1), Some("Beta"));
assert_eq!(target.declared_type_name("unnamed", 9), None);
}
#[test]
fn named_type_unwraps_option_and_vec() {
let nested = TypeRef::Optional(Box::new(TypeRef::Vec(Box::new(TypeRef::Named("Model".to_string())))));
assert_eq!(named_type(&nested), Some("Model"));
assert_eq!(named_type(&TypeRef::String), None);
}
#[test]
fn a_primitive_param_declares_no_named_type() {
let functions = vec![function(
"scale",
vec![param("factor", TypeRef::Primitive(PrimitiveType::F64))],
)];
let ir = CallIr {
functions: &functions,
type_defs: &[],
};
let target = TargetParams::resolve(&call_named("scale"), "dart", ir);
assert!(target.known().is_some());
assert_eq!(target.declared_type_name("factor", 0), None);
}
}