use crate::EmitEffects;
use crate::Planner;
use crate::context::expression::ExpressionContext;
use crate::names::generics::extract_type_mapping;
use crate::names::go_name;
use crate::state::bindings::BindingValue;
use syntax::types::{Type, unqualified_name};
pub(crate) enum IdentifierKind {
UnitValue,
PublicFunction { capitalized: String },
UnitConstructor { name: String, type_args: String },
ConstructorFunction { name: String, type_args: String },
Regular { name: String },
}
impl Planner<'_> {
pub(crate) fn emit_identifier(
&mut self,
value: &str,
ty: &Type,
ctx: ExpressionContext<'_>,
fx: &mut EmitEffects,
) -> String {
if let Some(BindingValue::InlineExpr(expr)) = self.scope.resolve_identifier_binding(value) {
return expr.as_str().to_string();
}
match self.classify_identifier(value, ty, ctx, fx) {
IdentifierKind::UnitValue => "struct{}{}".to_string(),
IdentifierKind::PublicFunction { capitalized } => capitalized,
IdentifierKind::UnitConstructor { name, type_args } => {
format!("{}{}()", self.resolve_go_name(&name, fx), type_args)
}
IdentifierKind::ConstructorFunction { name, type_args } => {
format!("{}{}", self.resolve_go_name(&name, fx), type_args)
}
IdentifierKind::Regular { name } => {
if let Some(expression) = self.try_emit_method_expression(&name, ty, fx) {
return expression;
}
let resolved = self.capitalize_static_method_if_public(&name);
let go_name = self.resolve_go_name(&resolved, fx);
if !ctx.is_callee()
&& let Some(type_args) = self.format_generic_value_type_args(&name, ty, fx)
{
return format!("{}{}", go_name, type_args);
}
go_name
}
}
}
fn classify_identifier(
&mut self,
value: &str,
ty: &Type,
ctx: ExpressionContext<'_>,
fx: &mut EmitEffects,
) -> IdentifierKind {
if value == "Unit" && ty.is_unit() {
return IdentifierKind::UnitValue;
}
let name = self
.scope
.resolve_binding_go_name(value)
.unwrap_or(value)
.to_string();
if let Some(capitalized) = self.try_capitalize_public_function(&name, ty) {
return IdentifierKind::PublicFunction { capitalized };
}
let mut make_fn = self.facts.make_function_name(&name);
if make_fn.is_none() {
let enum_id = match ty {
Type::Function(f) => {
if let Type::Nominal { id, .. } = f.return_type.as_ref() {
Some(id.as_str())
} else {
None
}
}
Type::Nominal { id, .. } => Some(id.as_str()),
_ => None,
};
if let Some(id) = enum_id {
let enum_name = unqualified_name(id);
let qualified = format!("{}.{}", enum_name, value);
make_fn = self.facts.make_function_name(&qualified);
}
}
if let Some(make_fn_value) = make_fn {
let name = make_fn_value.to_string();
match ty {
Type::Nominal { params, .. } => {
let type_args = match ctx.expected_slot_type() {
Some(t) => self
.prelude_container_type_args(t, fx)
.unwrap_or_else(|| self.format_type_args(params, fx)),
None => self.format_type_args(params, fx),
};
return IdentifierKind::UnitConstructor { name, type_args };
}
Type::Function(f) => {
if let Type::Nominal {
params: ret_params, ..
} = f.return_type.as_ref()
{
let type_args =
self.constructor_fn_type_args(&f.params, ret_params, ctx, fx);
return IdentifierKind::ConstructorFunction { name, type_args };
}
}
_ => unreachable!("make_fn set for unexpected type: {:?}", ty),
}
}
let resolved = make_fn.map(str::to_string).unwrap_or(name);
IdentifierKind::Regular { name: resolved }
}
fn constructor_fn_type_args(
&mut self,
fn_params: &[Type],
ret_params: &[Type],
ctx: ExpressionContext<'_>,
fx: &mut EmitEffects,
) -> String {
let needs_type_args = !ctx.is_callee()
|| ret_params.len() > fn_params.len()
|| !ret_params
.iter()
.all(|rp| fn_params.iter().any(|fp| fp.contains_type(rp)))
|| fn_params
.iter()
.any(|t| self.needs_explicit_args_for_go_inference(t));
if needs_type_args {
self.format_type_args(ret_params, fx)
} else {
String::new()
}
}
fn format_type_args_from_forall(
&mut self,
definition_ty: &Type,
instantiated_ty: &Type,
fx: &mut EmitEffects,
) -> Option<String> {
let Type::Forall { vars, body } = definition_ty else {
return None;
};
let mut mapping = rustc_hash::FxHashMap::default();
extract_type_mapping(body, instantiated_ty, &mut mapping);
let args: Vec<String> = vars
.iter()
.filter_map(|var| {
let concrete = mapping.get(var.as_str())?;
Some(self.go_type_string(concrete, fx))
})
.collect();
if args.len() != vars.len()
|| args.is_empty()
|| args.iter().any(|a| a.contains("interface{}"))
{
return None;
}
Some(format!("[{}]", args.join(", ")))
}
fn format_generic_value_type_args(
&mut self,
name: &str,
instantiated_ty: &Type,
fx: &mut EmitEffects,
) -> Option<String> {
let qualified_name = self.facts.qualified_current(name);
let definition_ty = self
.facts
.definition(qualified_name.as_str())
.or_else(|| {
let prelude_name = format!("{}.{}", go_name::PRELUDE_MODULE, name);
self.facts.definition(prelude_name.as_str())
})?
.ty();
self.format_type_args_from_forall(definition_ty, instantiated_ty, fx)
}
pub(crate) fn format_cross_module_type_args(
&mut self,
qualified_name: &str,
instantiated_ty: &Type,
fx: &mut EmitEffects,
) -> Option<String> {
let definition_ty = self.facts.definition(qualified_name)?.ty().clone();
self.format_type_args_from_forall(&definition_ty, instantiated_ty, fx)
}
fn try_emit_method_expression(
&mut self,
name: &str,
id_ty: &Type,
fx: &mut EmitEffects,
) -> Option<String> {
let (type_part, method_part) = name.split_once('.')?;
if method_part.contains('.') {
return None;
}
let fn_params = match id_ty {
Type::Function(f) => &f.params,
Type::Forall { body, .. } => match body.as_ref() {
Type::Function(f) => &f.params,
_ => return None,
},
_ => return None,
};
let real_type_part = self
.resolve_alias_type_name(type_part)
.unwrap_or_else(|| type_part.to_string());
let qualified_name = self.facts.qualified_current(&real_type_part);
let first = fn_params.first()?;
let stripped = first.strip_refs();
let is_self =
matches!(stripped, Type::Nominal { ref id, .. } if id.as_str() == qualified_name);
if !is_self {
return None;
}
let type_part = &real_type_part;
let is_pointer = first.is_ref();
if self.facts.is_ufcs_method(&qualified_name, method_part) {
return None;
}
let method_key = self.facts.qualified_current_member(type_part, method_part);
let should_export = self
.facts
.definition(method_key.as_str())
.map(|d| d.visibility().is_public())
.unwrap_or(false)
|| self.method_needs_export(method_part);
let go_method = if should_export {
go_name::snake_to_camel(method_part)
} else {
go_name::escape_keyword(method_part).into_owned()
};
let type_args = if let Type::Nominal { ref params, .. } = stripped {
if params.is_empty() {
String::new()
} else {
self.format_type_args(params, fx)
}
} else {
String::new()
};
if is_pointer {
Some(format!("(*{}{}).{}", type_part, type_args, go_method))
} else {
Some(format!("{}{}.{}", type_part, type_args, go_method))
}
}
pub(crate) fn try_resolve_cross_module_static_method(
&mut self,
name: &str,
fx: &mut EmitEffects,
) -> Option<String> {
if !name.contains('.') {
return None;
}
let last_dot = name.rfind('.')?;
let method_name = &name[last_dot + 1..];
let type_and_module = &name[..last_dot];
let type_name = if let Some(dot_position) = type_and_module.rfind('.') {
&type_and_module[dot_position + 1..]
} else if let Some(slash_position) = type_and_module.rfind('/') {
&type_and_module[slash_position + 1..]
} else {
return None;
};
let module_name = if let Some(dot_position) = type_and_module.rfind('.') {
type_and_module[..dot_position].to_string()
} else if let Some(slash_position) = type_and_module.rfind('/') {
type_and_module[..slash_position].to_string()
} else {
return None;
};
if self.facts.is_current_module(&module_name) {
return None;
}
let type_id = format!("{}.{}", module_name, type_name);
let method_key = format!("{}.{}", type_id, method_name);
let is_public = self
.facts
.definition(method_key.as_str())
.map(|d| d.visibility().is_public())
.unwrap_or(true)
|| self.method_needs_export(method_name);
Some(self.qualify_method_call(&type_id, method_name, is_public, fx))
}
fn try_capitalize_public_function(&self, name: &str, ty: &Type) -> Option<String> {
let is_function = matches!(ty, Type::Function(_))
|| matches!(ty, Type::Forall { body, .. } if matches!(body.as_ref(), Type::Function(_)));
if !is_function {
return None;
}
if self.scope.resolve_identifier_binding(name).is_some() {
return None;
}
if name.contains('.') {
return None;
}
let qualified_name = self.facts.qualified_current(name);
let definition = self.facts.definition(qualified_name.as_str())?;
if !definition.visibility().is_public() {
return None;
}
Some(go_name::snake_to_camel(name))
}
}