use crate::EmitEffects;
use crate::Planner;
use crate::abi::AbiShape;
use crate::abi::transition::emit_return_adapter;
use crate::names::go_name;
use crate::names::go_name::GO_IMPORT_PREFIX;
use crate::write_line;
use ecow::EcoString;
use syntax::program::{Definition, DefinitionBody, Interface};
use syntax::types::{SubstitutionMap, Type, build_substitution_map, substitute, unqualified_name};
pub(crate) struct AdapterPlan {
pub(crate) concrete_id: EcoString,
pub(crate) interface_id: EcoString,
pub(crate) concrete_ty: Type,
pub(crate) methods: Vec<AdapterMethod>,
}
pub(crate) struct AdapterMethod {
pub(crate) name: EcoString,
pub(crate) param_types: Vec<Type>,
pub(crate) return_type: Type,
pub(crate) user_shape: Option<AbiShape>,
pub(crate) interface_shape: Option<AbiShape>,
}
impl Planner<'_> {
pub(crate) fn lookup_struct_field_ty(
&self,
struct_ty: &Type,
field_name: &str,
) -> Option<Type> {
let stripped = struct_ty.strip_refs();
let Type::Nominal { id, params, .. } = &stripped else {
return None;
};
let Some(Definition {
body: DefinitionBody::Struct {
fields, generics, ..
},
..
}) = self.facts.definition(id.as_str())
else {
return None;
};
let field_ty = fields.iter().find(|f| f.name == field_name)?.ty.clone();
if generics.is_empty() {
return Some(field_ty);
}
let subst_map = build_substitution_map(generics, params);
Some(substitute(&field_ty, &subst_map))
}
pub(crate) fn is_function_alias(&self, ty: &Type) -> bool {
let Type::Nominal { .. } = ty else {
return false;
};
self.facts.resolve_to_function_type(ty).is_some()
}
fn collect_all_interface_methods(
&self,
root_id: &str,
iface: &Interface,
) -> Vec<(EcoString, Type, EcoString)> {
let mut result: Vec<(EcoString, Type, EcoString)> = Vec::new();
let mut seen: std::collections::HashSet<EcoString> = std::collections::HashSet::new();
let mut queue: Vec<(&Interface, EcoString)> = vec![(iface, EcoString::from(root_id))];
while let Some((current, current_id)) = queue.pop() {
for (name, ty) in ¤t.methods {
if seen.insert(name.clone()) {
result.push((name.clone(), ty.clone(), current_id.clone()));
}
}
for parent_ty in ¤t.parents {
let parent = self.facts.peel_alias(parent_ty);
let Type::Nominal { id, .. } = &parent else {
continue;
};
if let Some(Definition {
body:
DefinitionBody::Interface {
definition: parent_definition,
},
..
}) = self.facts.definition(id.as_str())
{
queue.push((parent_definition, id.as_eco().clone()));
}
}
}
result
}
pub(crate) fn needs_adapter(&self, source_ty: &Type, target_ty: &Type) -> Option<AdapterPlan> {
let target = self.facts.peel_alias(target_ty);
let Type::Nominal { id: target_id, .. } = &target else {
return None;
};
let Some(Definition {
body: DefinitionBody::Interface { definition },
..
}) = self.facts.definition(target_id.as_str())
else {
return None;
};
let source_stripped = source_ty.strip_refs();
let Type::Nominal {
id: source_id,
params: source_params,
..
} = &source_stripped
else {
return None;
};
if source_id.starts_with(GO_IMPORT_PREFIX) {
return None;
}
let Some(Definition {
body:
DefinitionBody::Struct {
generics: struct_generics,
methods: struct_methods,
..
},
..
}) = self.facts.definition(source_id.as_str())
else {
return None;
};
let subst_map = build_substitution_map(struct_generics, source_params);
let all_interface_methods = self.collect_all_interface_methods(target_id, definition);
let mut methods = Vec::with_capacity(all_interface_methods.len());
let mut any_adapted = false;
for (method_name, _interface_method_ty, declaring_id) in &all_interface_methods {
let impl_ty = struct_methods.get(method_name)?;
let (method, adapted) =
self.build_adapter_method(method_name, declaring_id, impl_ty, &subst_map)?;
if adapted {
any_adapted = true;
}
methods.push(method);
}
if !any_adapted {
return None;
}
Some(AdapterPlan {
concrete_id: source_id.as_eco().clone(),
interface_id: target_id.as_eco().clone(),
concrete_ty: source_ty.clone(),
methods,
})
}
fn build_adapter_method(
&self,
method_name: &EcoString,
declaring_id: &EcoString,
impl_ty: &Type,
subst_map: &SubstitutionMap,
) -> Option<(AdapterMethod, bool)> {
let Type::Function(f) = impl_ty.unwrap_forall() else {
return None;
};
let params = &f.params;
let param_types: Vec<Type> = if params.is_empty() {
Vec::new()
} else {
params[1..]
.iter()
.map(|p| substitute(p, subst_map))
.collect()
};
let return_type = substitute(&f.return_type, subst_map);
let user_shape = self.classify_direct_emission(&return_type);
let interface_hints = self.go_interface_method_hints(declaring_id, method_name);
let interface_shape = match user_shape.as_ref() {
Some(AbiShape::NullableReturn) if interface_hints.iter().any(|h| h == "comma_ok") => {
Some(AbiShape::CommaOk)
}
other => other.cloned(),
};
let adapted = user_shape != interface_shape;
Some((
AdapterMethod {
name: method_name.clone(),
param_types,
return_type,
user_shape,
interface_shape,
},
adapted,
))
}
fn emit_hint_shift_bridge(
&mut self,
inner_call: &str,
return_ty: &Type,
user_shape: &AbiShape,
interface_shape: &AbiShape,
fx: &mut EmitEffects,
) -> Option<(String, String)> {
let (AbiShape::NullableReturn, AbiShape::CommaOk) = (user_shape, interface_shape) else {
return None;
};
let inner = self.facts.peel_alias(return_ty).ok_type();
let is_interface = self.facts.is_interface(&inner);
let val = self.fresh_var(Some("val"));
self.declare(&val);
let nil_check = if is_interface {
fx.require_stdlib();
format!("!lisette.IsNilInterface({})", val)
} else {
format!("{} != nil", val)
};
let go_ret = self.render_lowered_return_ty(&AbiShape::CommaOk, return_ty, fx);
let body = format!("{val} := {inner_call}\nreturn {val}, {nil_check}\n");
Some((go_ret, body))
}
pub(crate) fn go_interface_method_hints(
&self,
interface_id: &str,
method_name: &str,
) -> Vec<String> {
let qualified = format!("{}.{}", interface_id, method_name);
self.facts
.definition(qualified.as_str())
.map(|d| d.go_hints().to_vec())
.unwrap_or_default()
}
pub(crate) fn classify_with_go_hints(
&self,
return_ty: &Type,
hints: &[String],
) -> Option<AbiShape> {
let base = self.classify_direct_emission(return_ty)?;
if matches!(base, AbiShape::NullableReturn) && hints.iter().any(|h| h == "comma_ok") {
return Some(AbiShape::CommaOk);
}
Some(base)
}
pub(crate) fn ensure_adapter_type(
&mut self,
plan: AdapterPlan,
fx: &mut EmitEffects,
) -> String {
let key = (
concrete_dedup_key(&plan.concrete_ty, &plan.concrete_id),
plan.interface_id.clone(),
);
if let Some(name) = self.adapter_registry.lookup(&key) {
return name.clone();
}
let index = self.adapter_registry.next_index();
let adapter_name = adapter_type_name(&plan, index);
let concrete_go_ty = self.go_type_string(&plan.concrete_ty, fx);
let mut declaration = String::new();
write_line!(declaration, "type {} struct {{", adapter_name);
write_line!(declaration, "inner {}", concrete_go_ty);
write_line!(declaration, "}}");
declaration.push('\n');
for method in &plan.methods {
self.emit_adapter_method(&mut declaration, &adapter_name, method, fx);
declaration.push('\n');
}
self.adapter_registry
.insert(key, adapter_name.clone(), declaration);
adapter_name
}
fn emit_adapter_method(
&mut self,
declaration: &mut String,
adapter_name: &str,
method: &AdapterMethod,
fx: &mut EmitEffects,
) {
self.enter_scope();
let param_names: Vec<String> = (0..method.param_types.len())
.map(|i| format!("arg{}", i))
.collect();
for name in ¶m_names {
self.declare(name);
}
let params_str = param_names
.iter()
.zip(method.param_types.iter())
.map(|(n, t)| format!("{} {}", n, self.go_type_string(t, fx)))
.collect::<Vec<_>>()
.join(", ");
let go_method_name = if self.method_needs_export(&method.name) {
go_name::snake_to_camel(&method.name)
} else {
go_name::escape_keyword(&method.name).into_owned()
};
let inner_call = format!("a.inner.{}({})", go_method_name, param_names.join(", "));
let (go_ret, body) = self.build_adapter_body(method, &inner_call, fx);
self.finish_adapter_method(
declaration,
adapter_name,
&go_method_name,
¶ms_str,
&go_ret,
&body,
);
}
fn build_adapter_body(
&mut self,
method: &AdapterMethod,
inner_call: &str,
fx: &mut EmitEffects,
) -> (String, String) {
let user_shape = &method.user_shape;
let interface_shape = &method.interface_shape;
if user_shape == interface_shape
&& let Some(shape) = user_shape
{
let go_ret = self.render_lowered_return_ty(shape, &method.return_type, fx);
return (go_ret, format!("return {}\n", inner_call));
}
if let (Some(user), Some(interface)) = (user_shape, interface_shape)
&& user != interface
&& let Some(bridge) =
self.emit_hint_shift_bridge(inner_call, &method.return_type, user, interface, fx)
{
return bridge;
}
let adapter = emit_return_adapter(self, inner_call, &method.return_type, fx);
if let Some(adapter) = adapter {
return adapter;
}
if method.return_type.is_unit() {
(String::new(), format!("{}\n", inner_call))
} else {
let ret = self.go_type_string(&method.return_type, fx);
(ret, format!("return {}\n", inner_call))
}
}
fn finish_adapter_method(
&mut self,
declaration: &mut String,
adapter_name: &str,
method_name: &str,
params: &str,
go_ret: &str,
body: &str,
) {
write_method_header(declaration, adapter_name, method_name, params, go_ret);
declaration.push_str(body);
write_line!(declaration, "}}");
self.exit_scope();
}
pub(crate) fn resolve_tuple_slot_types(
&mut self,
inferred: Vec<Type>,
in_tail: bool,
) -> Vec<Type> {
let resolved = self.return_ctx();
let return_slots = resolved.ty().and_then(|ty| {
let Type::Tuple(slots) = ty else {
return None;
};
(slots.len() == inferred.len()).then(|| slots.clone())
});
let Some(return_slots) = return_slots else {
return inferred;
};
if in_tail {
return return_slots;
}
return_slots
.iter()
.zip(inferred.iter())
.map(|(declared, inferred_slot)| {
let needs_widening = self.needs_adapter(inferred_slot, declared).is_some()
|| self.facts.is_interface(declared)
|| (declared.get_qualified_id().is_some()
&& declared.get_qualified_id() == inferred_slot.get_qualified_id());
if needs_widening {
declared.clone()
} else {
inferred_slot.clone()
}
})
.collect()
}
}
fn write_method_header(
declaration: &mut String,
adapter_name: &str,
method_name: &str,
params: &str,
go_ret: &str,
) {
let ret_suffix = if go_ret.is_empty() {
String::new()
} else {
format!(" {}", go_ret)
};
write_line!(
declaration,
"func (a {}) {}({}){} {{",
adapter_name,
method_name,
params,
ret_suffix
);
}
fn concrete_dedup_key(concrete_ty: &Type, concrete_id: &EcoString) -> EcoString {
let mut depth = 0usize;
let mut t = concrete_ty.clone();
while t.is_ref() {
depth += 1;
t = t.inner().expect("Ref<T> must have inner").clone();
}
let params = match &t {
Type::Nominal { params, .. } if !params.is_empty() => Some(params),
_ => None,
};
let params_suffix = params
.map(|ps| {
let joined = ps
.iter()
.map(|p| p.to_string())
.collect::<Vec<_>>()
.join(",");
format!("<{joined}>")
})
.unwrap_or_default();
EcoString::from(format!(
"{}{}{}",
"*".repeat(depth),
concrete_id.as_str(),
params_suffix
))
}
fn adapter_type_name(plan: &AdapterPlan, index: usize) -> String {
let concrete_name = plan
.concrete_id
.rsplit('.')
.next()
.unwrap_or(plan.concrete_id.as_str());
let go_path = plan
.interface_id
.strip_prefix(GO_IMPORT_PREFIX)
.unwrap_or(plan.interface_id.as_str());
let iface_name = unqualified_name(go_path);
format!(
"{}{}_{}_{}",
go_name::ADAPTER_TYPE_PREFIX,
concrete_name,
iface_name,
index
)
}