use crate::EmitEffects;
use crate::Planner;
use crate::definitions::enum_layout::{ENUM_GO_STRINGER_METHOD, ENUM_STRINGER_METHOD};
use crate::definitions::tags::{format_tag_string, interpret_field_attributes};
use crate::expressions::top_items::emit_doc;
use crate::names::generics::receiver_generics_string;
use crate::names::go_name;
use crate::utils::receiver_name;
use syntax::ast::{Attribute, Generic, StructFieldDefinition, StructKind};
use syntax::program::{Definition, DefinitionBody};
use syntax::types::Type;
impl Planner<'_> {
pub(crate) fn emit_struct_definition(
&mut self,
name: &str,
generics: &[Generic],
fields: &[StructFieldDefinition],
kind: &StructKind,
struct_attrs: &[Attribute],
fx: &mut EmitEffects,
) -> String {
let symbol = self.facts.qualified_current(name);
let generics_string = self.generics_to_string_for_symbol(&symbol, generics, fx);
if *kind == StructKind::Tuple {
return self.emit_tuple_struct(
name,
&generics_string,
fields,
generics,
struct_attrs,
fx,
);
}
let mut field_strings: Vec<String> = Vec::with_capacity(fields.len());
let mut stringer_fields: Vec<StringerField> = Vec::with_capacity(fields.len());
for f in fields {
let (field_string, stringer_field) = self.emit_struct_field(f, name, struct_attrs, fx);
field_strings.push(field_string);
stringer_fields.push(stringer_field);
}
let receiver_generics = receiver_generics_string(generics);
let go_type_name = go_name::escape_keyword(name);
let definition = if field_strings.is_empty() {
format!("type {}{} struct{{}}", go_type_name, generics_string)
} else {
format!(
"type {}{} struct {{\n{}\n}}",
go_type_name,
generics_string,
field_strings.join("\n")
)
};
let mut result = if let Some(stringer_name) = self.stringer_method_name(name, struct_attrs)
{
let string_method = emit_struct_stringer_method(
name,
&receiver_generics,
&stringer_fields,
stringer_name,
);
if !stringer_fields.is_empty() {
fx.require_fmt();
}
format!("{definition}\n\n{string_method}")
} else {
definition
};
self.append_to_string_method(&mut result, name, &receiver_generics, struct_attrs);
result
}
fn emit_tuple_struct(
&mut self,
name: &str,
generics_string: &str,
fields: &[StructFieldDefinition],
generics: &[Generic],
struct_attrs: &[Attribute],
fx: &mut EmitEffects,
) -> String {
let definition = self.emit_tuple_struct_definition(name, generics_string, fields, fx);
let receiver_generics = receiver_generics_string(generics);
let mut result = definition;
if self.is_pointer_backed_newtype(name) {
return result;
}
if let Some(stringer_name) = self.stringer_method_name(name, struct_attrs) {
let is_type_alias = fields.len() == 1 && generics_string.is_empty();
let underlying_go_type = is_type_alias.then(|| self.go_type_string(&fields[0].ty, fx));
let string_method = emit_tuple_struct_stringer_method(
name,
&receiver_generics,
fields.len(),
underlying_go_type.as_deref(),
stringer_name,
);
if !string_method.is_empty() {
if string_method.contains("fmt.") {
fx.require_fmt();
}
result.push_str("\n\n");
result.push_str(&string_method);
}
}
self.append_to_string_method(&mut result, name, &receiver_generics, struct_attrs);
result
}
fn emit_struct_field(
&mut self,
f: &StructFieldDefinition,
struct_name: &str,
struct_attrs: &[Attribute],
fx: &mut EmitEffects,
) -> (String, StringerField) {
if f.embedded {
let field_with_doc = format!("{}{}", emit_doc(&f.doc), self.go_type_string(&f.ty, fx));
let stringer_field = StringerField {
source_name: f.name.to_string(),
go_name: struct_field_go_name(f, struct_attrs),
is_function: is_raw_function_type(&f.ty),
};
return (field_with_doc, stringer_field);
}
let tag_configs = interpret_field_attributes(f, struct_attrs);
let needs_omitzero = is_option_type(&f.ty);
let tag_string = format_tag_string(&f.name, &tag_configs, needs_omitzero);
let has_tags = !tag_configs.is_empty();
let field_name = struct_field_go_name(f, struct_attrs);
if has_tags && !f.visibility.is_public() {
let key = self.facts.qualified_current_member(struct_name, &f.name);
self.module.record_tag_exported_field(key);
}
let field_definition = if let Some(tags) = tag_string {
format!("{} {} {}", field_name, self.go_type_string(&f.ty, fx), tags)
} else {
format!("{} {}", field_name, self.go_type_string(&f.ty, fx))
};
let field_with_doc = format!("{}{}", emit_doc(&f.doc), field_definition);
let stringer_field = StringerField {
source_name: f.name.to_string(),
go_name: field_name,
is_function: is_raw_function_type(&f.ty),
};
(field_with_doc, stringer_field)
}
fn emit_tuple_struct_definition(
&mut self,
name: &str,
generics_string: &str,
fields: &[StructFieldDefinition],
fx: &mut EmitEffects,
) -> String {
let go_type_name = go_name::escape_keyword(name);
if fields.is_empty() {
return format!("type {}{} struct{{}}", go_type_name, generics_string);
}
if fields.len() == 1 && generics_string.is_empty() {
let underlying = self.go_type_string(&fields[0].ty, fx);
return format!("type {} {}", go_type_name, underlying);
}
let field_strings: Vec<String> = fields
.iter()
.enumerate()
.map(|(i, f)| format!("F{} {}", i, self.go_type_string(&f.ty, fx)))
.collect();
format!(
"type {}{} struct {{\n{}\n}}",
go_type_name,
generics_string,
field_strings.join("\n")
)
}
pub(crate) fn stringer_overrides(&self, name: &str) -> (bool, bool) {
let qualified = self.facts.qualified_current(name);
let methods = self
.facts
.definition(qualified.as_str())
.and_then(|definition| match &definition.body {
DefinitionBody::Struct { methods, .. }
| DefinitionBody::Enum { methods, .. }
| DefinitionBody::TypeAlias { methods, .. } => Some(methods),
_ => None,
});
let is_user_stringer = |method_name: &str| {
methods.is_some_and(|m| m.get(method_name).is_some_and(Type::is_stringer_signature))
&& !self.facts.is_ufcs_method(&qualified, method_name)
};
let has_stringer = is_user_stringer("string") || is_user_stringer(ENUM_STRINGER_METHOD);
let has_go_stringer =
is_user_stringer("goString") || is_user_stringer(ENUM_GO_STRINGER_METHOD);
(has_stringer, has_go_stringer)
}
pub(crate) fn should_synthesize_to_string(&self, name: &str, attributes: &[Attribute]) -> bool {
attributes.iter().any(|a| a.name == "display") && !self.module.has_user_to_string(name)
}
pub(crate) fn is_pointer_backed_newtype(&self, name: &str) -> bool {
let qualified = self.facts.qualified_current(name);
self.facts
.definition(qualified.as_str())
.is_some_and(|definition| {
definition.is_pointer_backed_newtype(|id| {
self.facts
.definition(id)
.is_some_and(Definition::is_type_alias)
})
})
}
pub(crate) fn to_string_method_go_name(&self) -> String {
if self.method_needs_export("to_string") {
go_name::snake_to_camel("to_string")
} else {
go_name::escape_keyword("to_string").into_owned()
}
}
pub(crate) fn append_to_string_method(
&self,
out: &mut String,
name: &str,
receiver_generics: &str,
attributes: &[Attribute],
) {
if self.should_synthesize_to_string(name, attributes) {
let go_method = self.to_string_method_go_name();
out.push_str("\n\n");
out.push_str(&emit_to_string_method(name, receiver_generics, &go_method));
}
}
pub(crate) fn stringer_method_name(
&self,
name: &str,
attributes: &[Attribute],
) -> Option<&'static str> {
if !should_synthesize_stringer(attributes) {
return None;
}
match self.stringer_overrides(name) {
(true, true) => None,
(true, false) => Some(ENUM_GO_STRINGER_METHOD),
_ => Some(ENUM_STRINGER_METHOD),
}
}
}
pub(crate) fn should_synthesize_stringer(attributes: &[Attribute]) -> bool {
attributes.iter().any(|a| a.name == "display")
}
pub(crate) fn struct_field_go_name(
field: &StructFieldDefinition,
struct_attrs: &[Attribute],
) -> String {
if field.embedded {
return go_name::escape_keyword(&field.name).into_owned();
}
let needs_export =
field.visibility.is_public() || !interpret_field_attributes(field, struct_attrs).is_empty();
if needs_export {
go_name::make_exported(&field.name)
} else {
go_name::escape_keyword(&field.name).into_owned()
}
}
pub(crate) struct StringerField {
source_name: String,
go_name: String,
is_function: bool,
}
pub(crate) fn is_raw_function_type(ty: &Type) -> bool {
match ty {
Type::Function(_) => true,
Type::Forall { body, .. } => is_raw_function_type(body),
_ => false,
}
}
pub(crate) fn stringer_verb(is_function: bool) -> &'static str {
if is_function { "%p" } else { "%v" }
}
fn is_option_type(ty: &Type) -> bool {
match ty {
Type::Nominal {
id, underlying_ty, ..
} => {
if id == "Option" || id.ends_with(".Option") {
return true;
}
underlying_ty.as_deref().is_some_and(is_option_type)
}
_ => false,
}
}
fn emit_to_string_method(name: &str, receiver_generics: &str, method_name: &str) -> String {
let receiver = receiver_name(name);
let go_type_name = go_name::escape_keyword(name);
let receiver_type = format!("{go_type_name}{receiver_generics}");
format!(
"func ({receiver} {receiver_type}) {method_name}() string {{\nreturn {receiver}.String()\n}}"
)
}
fn emit_struct_stringer_method(
name: &str,
receiver_generics: &str,
fields: &[StringerField],
method_name: &str,
) -> String {
let receiver = receiver_name(name);
let go_type_name = go_name::escape_keyword(name);
let receiver_type = format!("{go_type_name}{receiver_generics}");
if fields.is_empty() {
return format!(
"func ({receiver} {receiver_type}) {method_name}() string {{\nreturn \"{name}\"\n}}"
);
}
let format_parts: Vec<String> = fields
.iter()
.map(|f| format!("{}: {}", f.source_name, stringer_verb(f.is_function)))
.collect();
let args: Vec<String> = fields
.iter()
.map(|f| format!("{receiver}.{}", f.go_name))
.collect();
format!(
"func ({receiver} {receiver_type}) {method_name}() string {{\nreturn fmt.Sprintf(\"{name} {{ {} }}\", {})\n}}",
format_parts.join(", "),
args.join(", ")
)
}
fn emit_tuple_struct_stringer_method(
name: &str,
receiver_generics: &str,
field_count: usize,
underlying_go_type: Option<&str>,
method_name: &str,
) -> String {
let receiver = receiver_name(name);
let go_type_name = go_name::escape_keyword(name);
let receiver_type = format!("{go_type_name}{receiver_generics}");
if field_count == 0 {
return format!(
"func ({receiver} {receiver_type}) {method_name}() string {{\nreturn \"{name}\"\n}}"
);
}
if let Some(underlying) = underlying_go_type {
return format!(
"func ({receiver} {receiver_type}) {method_name}() string {{\nreturn fmt.Sprintf(\"{name}(%v)\", {underlying}({receiver}))\n}}"
);
}
let placeholders: Vec<&str> = (0..field_count).map(|_| "%v").collect();
let args: Vec<String> = (0..field_count)
.map(|i| format!("{receiver}.F{i}"))
.collect();
format!(
"func ({receiver} {receiver_type}) {method_name}() string {{\nreturn fmt.Sprintf(\"{name}({})\", {})\n}}",
placeholders.join(", "),
args.join(", ")
)
}