use syntax::ast::{Expression, Span, StructKind, UnaryOperator};
use syntax::program::{Definition, DotAccessKind as SemanticDotKind, ReceiverCoercion};
use syntax::types::Type;
use crate::Emitter;
use crate::go_name;
use crate::write_line;
impl Emitter<'_> {
pub(crate) fn emit_dot_access(
&mut self,
output: &mut String,
expression: &Expression,
member: &str,
result_ty: &Type,
span: Span,
) -> String {
let dot_access_kind = self.ctx.resolutions.get_dot_access(span);
if let Some(s) =
self.try_emit_pre_receiver_dot(expression, member, result_ty, dot_access_kind)
{
return s;
}
let expression_string = self.emit_coerced_expression(output, expression);
let expression_ty = expression.get_type();
if let Some(s) = self.try_emit_tuple_member_dot(
&expression_string,
&expression_ty,
member,
dot_access_kind,
) {
return s;
}
let is_exported =
self.resolve_is_exported(expression, &expression_ty, member, dot_access_kind);
let field = go_field_name(&expression_ty, member, is_exported);
if let Some(s) = self.try_emit_nullable_field_access(
output,
&expression_string,
&field,
&expression_ty,
result_ty,
) {
return s;
}
let result = format!("{}.{}", expression_string, field);
self.append_cross_module_type_args(result, &expression_ty, member, result_ty)
}
fn try_emit_pre_receiver_dot(
&mut self,
expression: &Expression,
member: &str,
result_ty: &Type,
dot_access_kind: Option<SemanticDotKind>,
) -> Option<String> {
match dot_access_kind {
Some(SemanticDotKind::ValueEnumVariant) => {
self.emit_value_enum_variant(expression, member)
}
Some(SemanticDotKind::EnumVariant) => {
self.emit_enum_variant_dot(expression, member, result_ty)
}
Some(SemanticDotKind::StaticMethod { .. }) => {
self.emit_static_method_dot(expression, member, result_ty)
}
Some(SemanticDotKind::InstanceMethodValue {
is_exported,
is_pointer_receiver,
}) => self.emit_instance_method_value_dot(
expression,
member,
result_ty,
is_exported,
is_pointer_receiver,
),
Some(SemanticDotKind::ModuleMember) | None => self
.emit_enum_variant_dot(expression, member, result_ty)
.or_else(|| self.emit_static_method_dot(expression, member, result_ty)),
_ => None,
}
}
fn try_emit_tuple_member_dot(
&mut self,
expression_string: &str,
expression_ty: &Type,
member: &str,
dot_access_kind: Option<SemanticDotKind>,
) -> Option<String> {
let Ok(index) = member.parse::<usize>() else {
return None;
};
match dot_access_kind {
Some(SemanticDotKind::TupleElement) => {
let field = syntax::parse::TUPLE_FIELDS
.get(index)
.expect("oversize tuple arity");
Some(format!("{}.{}", expression_string, field))
}
Some(SemanticDotKind::TupleStructField { is_newtype }) => {
if is_newtype
&& let Some(cast) = self.try_emit_newtype_cast(expression_ty, expression_string)
{
return Some(cast);
}
Some(format!("{}.F{}", expression_string, index))
}
_ => None,
}
}
fn resolve_is_exported(
&self,
expression: &Expression,
expression_ty: &Type,
member: &str,
dot_access_kind: Option<SemanticDotKind>,
) -> bool {
match dot_access_kind {
Some(SemanticDotKind::StructField { is_exported }) => {
is_exported || self.field_is_public(expression_ty, member)
}
Some(SemanticDotKind::InstanceMethod { is_exported }) => {
is_exported || self.method_needs_export(member)
}
_ => {
self.compute_is_exported_context(expression, expression_ty)
|| self.field_is_public(expression_ty, member)
|| (!self.has_field(expression_ty, member) && self.method_needs_export(member))
}
}
}
fn try_emit_nullable_field_access(
&mut self,
output: &mut String,
expression_string: &str,
field: &str,
expression_ty: &Type,
result_ty: &Type,
) -> Option<String> {
if !Self::is_go_imported_type(expression_ty) || !self.is_go_nullable(result_ty) {
return None;
}
let raw_access = format!("{}.{}", expression_string, field);
let raw_var = self.fresh_var(Some("raw"));
self.declare(&raw_var);
write_line!(output, "{} := {}", raw_var, raw_access);
Some(self.maybe_wrap_go_nullable(output, &raw_var, result_ty))
}
fn append_cross_module_type_args(
&mut self,
base_access: String,
expression_ty: &Type,
member: &str,
result_ty: &Type,
) -> String {
if self.emitting_call_callee {
return base_access;
}
let resolved_expression_ty = expression_ty.resolve();
let Type::Constructor { ref id, .. } = resolved_expression_ty else {
return base_access;
};
let Some(module) = id.strip_prefix(go_name::IMPORT_PREFIX) else {
return base_access;
};
let qualified = format!("{}.{}", module, member);
match self.format_cross_module_type_args(&qualified, result_ty) {
Some(type_args) => format!("{}{}", base_access, type_args),
None => base_access,
}
}
fn try_emit_newtype_cast(
&mut self,
expression_ty: &Type,
expression_string: &str,
) -> Option<String> {
let deref_ty = expression_ty.resolve().strip_refs();
let Type::Constructor { id, .. } = &deref_ty else {
return None;
};
let Some(Definition::Struct { fields, .. }) = self.ctx.definitions.get(id.as_str()) else {
return None;
};
let field_ty = fields.first()?.ty.clone();
let go_type = self.go_type_as_string(&field_ty);
let operand = if expression_ty.resolve().is_ref() {
format!("*{}", expression_string)
} else {
expression_string.to_string()
};
Some(if go_type.starts_with('*') {
format!("({})({})", go_type, operand)
} else {
format!("{}({})", go_type, operand)
})
}
fn compute_is_exported_context(&self, expression: &Expression, expression_ty: &Type) -> bool {
matches!(
expression,
Expression::Identifier { ty: Type::Constructor { id, .. }, .. } if id.starts_with(go_name::IMPORT_PREFIX)
) || self.is_from_prelude(expression_ty)
|| if let Type::Constructor { id, .. } = expression_ty.resolve().strip_refs() {
id.split_once('.')
.is_some_and(|(m, _)| m != self.current_module && m != go_name::PRELUDE_MODULE)
} else {
false
}
}
fn emit_coerced_expression(&mut self, output: &mut String, expression: &Expression) -> String {
let coercion = self.ctx.coercions.get_coercion(expression.get_span());
let (expression_string, had_explicit_deref) = if let Expression::Unary {
operator: UnaryOperator::Deref,
expression: inner,
..
} = expression
{
(self.emit_operand(output, inner), true)
} else {
(self.emit_operand(output, expression), false)
};
let is_absorbed_ref = self.is_absorbed_ref_generic(expression);
match (coercion, had_explicit_deref) {
_ if is_absorbed_ref => expression_string,
(Some(ReceiverCoercion::AutoAddress), true) => expression_string,
(Some(ReceiverCoercion::AutoAddress), false) => match expression.unwrap_parens() {
Expression::Call { .. } => {
let tmp = self.fresh_var(Some("ref"));
self.declare(&tmp);
write_line!(output, "{} := {}", tmp, expression_string);
tmp
}
Expression::StructCall { .. } => format!("(&{})", expression_string),
_ => expression_string,
},
(Some(ReceiverCoercion::AutoDeref), _) => expression_string,
(None, true) => expression_string,
(None, false) => expression_string,
}
}
fn is_absorbed_ref_generic(&self, expression: &Expression) -> bool {
let check_expression = if let Expression::Unary {
operator: UnaryOperator::Deref,
expression: inner,
..
} = expression
{
inner.as_ref()
} else {
expression
};
let expression_ty = check_expression.get_type().resolve();
expression_ty.is_ref()
&& expression_ty.inner().is_some_and(|inner| {
matches!(inner.resolve(), Type::Parameter(name)
if self.module.absorbed_ref_generics.contains(name.as_ref()))
})
}
pub(crate) fn try_emit_tuple_struct_field_access(
&mut self,
expression_string: &str,
expression_ty: &Type,
index: usize,
) -> Option<String> {
let deref_ty = expression_ty.resolve().strip_refs();
let Type::Constructor { ref id, .. } = deref_ty else {
return None;
};
let Some(Definition::Struct {
kind,
fields,
generics,
..
}) = self.ctx.definitions.get(id.as_str())
else {
return None;
};
if *kind != StructKind::Tuple {
return None;
}
if fields.len() == 1 && generics.is_empty() {
let underlying_ty = self.go_type_as_string(&fields[0].ty);
let expression = if expression_ty.resolve().is_ref() {
format!("*{}", expression_string)
} else {
expression_string.to_string()
};
return Some(format!("{}({})", underlying_ty, expression));
}
Some(format!("{}.F{}", expression_string, index))
}
pub(super) fn is_from_prelude(&self, ty: &Type) -> bool {
let Type::Constructor { id, .. } = ty.resolve().strip_refs() else {
return false;
};
id.starts_with(go_name::PRELUDE_PREFIX)
}
}
fn go_field_name(expression_ty: &Type, member: &str, is_exported: bool) -> String {
let is_prelude_type = expression_ty
.resolve()
.strip_refs()
.get_qualified_id()
.is_some_and(|id| id.starts_with(go_name::PRELUDE_PREFIX));
if !is_exported {
return go_name::escape_keyword(member).into_owned();
}
if is_prelude_type {
go_name::snake_to_camel(member)
} else {
go_name::make_exported(member)
}
}