use syntax::ast::{Expression, StructKind, UnaryOperator};
use syntax::program::{
Definition, DefinitionBody, DotAccessKind as SemanticDotKind, ReceiverCoercion,
};
use syntax::types::Type;
use crate::Emitter;
use crate::expressions::context::ExpressionContext;
use crate::go_name;
use crate::types::coercion::{Coercion, CoercionDirection};
impl Emitter<'_> {
pub(crate) fn emit_dot_access(
&mut self,
output: &mut String,
dot_access: &Expression,
ctx: ExpressionContext<'_>,
) -> String {
let Expression::DotAccess {
expression,
member,
ty: result_ty,
dot_access_kind,
receiver_coercion,
..
} = dot_access
else {
unreachable!("emit_dot_access requires a DotAccess expression");
};
let dot_access_kind = *dot_access_kind;
let receiver_coercion = *receiver_coercion;
if let Some(s) =
self.try_emit_pre_receiver_dot(expression, member, result_ty, dot_access_kind, ctx)
{
return s;
}
let expression_string =
self.emit_coerced_expression(output, expression, receiver_coercion, ctx);
let expression_ty = expression.get_type();
if let Some(module) = expression_ty.as_import_namespace() {
self.require_module_import(module);
}
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, ctx)
}
fn try_emit_pre_receiver_dot(
&mut self,
expression: &Expression,
member: &str,
result_ty: &Type,
dot_access_kind: Option<SemanticDotKind>,
ctx: ExpressionContext<'_>,
) -> 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, ctx)
}
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, ctx)),
_ => 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.hoist_tmp_value(output, "raw", &raw_access);
let coercion = Coercion::resolve(
self,
result_ty,
result_ty,
CoercionDirection::FromGoBoundary,
);
Some(coercion.apply(self, output, raw_var))
}
fn append_cross_module_type_args(
&mut self,
base_access: String,
expression_ty: &Type,
member: &str,
result_ty: &Type,
ctx: ExpressionContext<'_>,
) -> String {
if ctx.is_callee() {
return base_access;
}
let Some(module) = expression_ty.as_import_namespace() 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.strip_refs();
let Type::Nominal { id, .. } = &deref_ty else {
return None;
};
let Some(Definition {
body: DefinitionBody::Struct { fields, .. },
..
}) = self.facts.definition(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.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 {
let is_import_namespace_ident = matches!(
expression,
Expression::Identifier { ty, .. } if ty.as_import_namespace().is_some()
);
is_import_namespace_ident
|| self.is_from_prelude(expression_ty)
|| if let Type::Nominal { id, .. } = expression_ty.strip_refs() {
id.split_once('.')
.is_some_and(|(m, _)| self.facts.is_foreign_module(m))
} else {
false
}
}
fn emit_coerced_expression(
&mut self,
output: &mut String,
expression: &Expression,
coercion: Option<ReceiverCoercion>,
ctx: ExpressionContext<'_>,
) -> String {
let (expression_string, had_explicit_deref) = if let Expression::Unary {
operator: UnaryOperator::Deref,
expression: inner,
..
} = expression
{
(self.emit_operand(output, inner, ctx), true)
} else {
(self.emit_operand(output, expression, ctx), 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 { .. } => self.hoist_tmp_value(output, "ref", &expression_string),
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();
expression_ty.is_ref()
&& expression_ty.inner().is_some_and(|inner| {
matches!(inner, Type::Parameter(name)
if self.function_state.is_absorbed_ref_generic(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.strip_refs();
let Type::Nominal { ref id, .. } = deref_ty else {
return None;
};
let Some(Definition {
body:
DefinitionBody::Struct {
kind,
fields,
generics,
..
},
..
}) = self.facts.definition(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.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::Nominal { id, .. } = ty.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 {
if expression_ty
.as_import_namespace()
.is_some_and(go_name::is_go_import)
{
return member.to_string();
}
let is_prelude_type = expression_ty
.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)
}
}