use crate::hir::*;
use crate::lifetime_analysis::LifetimeInference;
use crate::rust_gen::context::{CodeGenContext, RustCodeGen};
use crate::rust_gen::generator_gen::codegen_generator_function;
use crate::rust_gen::type_gen::{rust_type_to_syn, update_import_needs};
use anyhow::Result;
use quote::quote;
use syn::{self, parse_quote};
use super::analyze_mutable_vars;
#[inline]
pub(crate) fn codegen_generic_params(
type_params: &[crate::generic_inference::TypeParameter],
lifetime_params: &[String],
) -> proc_macro2::TokenStream {
if type_params.is_empty() && lifetime_params.is_empty() {
return quote! {};
}
let mut all_params = Vec::new();
for lt in lifetime_params {
if lt != "'static" {
let lt_ident = syn::Lifetime::new(lt, proc_macro2::Span::call_site());
all_params.push(quote! { #lt_ident });
}
}
for type_param in type_params {
let param_name = syn::Ident::new(&type_param.name, proc_macro2::Span::call_site());
if type_param.bounds.is_empty() {
all_params.push(quote! { #param_name });
} else {
let bounds: Vec<_> = type_param
.bounds
.iter()
.map(|b| {
let bound: syn::Path =
syn::parse_str(b).unwrap_or_else(|_| parse_quote! { Clone });
quote! { #bound }
})
.collect();
all_params.push(quote! { #param_name: #(#bounds)+* });
}
}
quote! { <#(#all_params),*> }
}
#[inline]
pub(crate) fn codegen_where_clause(
lifetime_bounds: &[(String, String)],
) -> proc_macro2::TokenStream {
if lifetime_bounds.is_empty() {
return quote! {};
}
let bounds: Vec<_> = lifetime_bounds
.iter()
.map(|(from, to)| {
let from_lt = syn::Lifetime::new(from, proc_macro2::Span::call_site());
let to_lt = syn::Lifetime::new(to, proc_macro2::Span::call_site());
quote! { #from_lt: #to_lt }
})
.collect();
quote! { where #(#bounds),* }
}
#[inline]
pub(crate) fn codegen_function_attrs(
docstring: &Option<String>,
properties: &crate::hir::FunctionProperties,
) -> Vec<proc_macro2::TokenStream> {
let mut attrs = vec![];
if let Some(docstring) = docstring {
attrs.push(quote! {
#[doc = #docstring]
});
}
if properties.panic_free {
attrs.push(quote! {
#[doc = " Depyler: verified panic-free"]
});
}
if properties.always_terminates {
attrs.push(quote! {
#[doc = " Depyler: proven to terminate"]
});
}
attrs
}
#[inline]
pub(crate) fn codegen_function_body(
func: &HirFunction,
can_fail: bool,
ctx: &mut CodeGenContext,
) -> Result<Vec<proc_macro2::TokenStream>> {
ctx.enter_scope();
ctx.current_function_can_fail = can_fail;
ctx.current_return_type = Some(func.ret_type.clone());
for param in &func.params {
ctx.declare_var(¶m.name);
ctx.var_types.insert(param.name.clone(), param.ty.clone());
}
analyze_mutable_vars(&func.body, ctx);
let body_stmts: Vec<_> = func
.body
.iter()
.map(|stmt| stmt.to_rust_tokens(ctx))
.collect::<Result<Vec<_>>>()?;
ctx.exit_scope();
ctx.current_function_can_fail = false;
ctx.current_return_type = None;
Ok(body_stmts)
}
#[inline]
pub(crate) fn codegen_function_params(
func: &HirFunction,
lifetime_result: &crate::lifetime_analysis::LifetimeResult,
ctx: &mut CodeGenContext,
) -> Result<Vec<proc_macro2::TokenStream>> {
func.params
.iter()
.map(|param| codegen_single_param(param, func, lifetime_result, ctx))
.collect()
}
fn codegen_single_param(
param: &HirParam,
func: &HirFunction,
lifetime_result: &crate::lifetime_analysis::LifetimeResult,
ctx: &mut CodeGenContext,
) -> Result<proc_macro2::TokenStream> {
let param_ident = syn::Ident::new(¶m.name, proc_macro2::Span::call_site());
let is_param_mutated = matches!(
lifetime_result.borrowing_strategies.get(¶m.name),
Some(crate::borrowing_context::BorrowingStrategy::TakeOwnership)
) && func.body.iter().any(
|stmt| matches!(stmt, HirStmt::Assign { target: AssignTarget::Symbol(s), .. } if s == ¶m.name),
);
if let Some(inferred) = lifetime_result.param_lifetimes.get(¶m.name) {
let rust_type = &inferred.rust_type;
let actual_rust_type =
if let crate::type_mapper::RustType::Enum { name, variants: _ } = rust_type {
if name == "UnionType" {
if let Type::Union(types) = ¶m.ty {
let enum_name = ctx.process_union_type(types);
crate::type_mapper::RustType::Custom(enum_name)
} else {
rust_type.clone()
}
} else {
rust_type.clone()
}
} else {
rust_type.clone()
};
update_import_needs(ctx, &actual_rust_type);
let ty = apply_param_borrowing_strategy(
¶m.name,
&actual_rust_type,
inferred,
lifetime_result,
ctx,
)?;
Ok(if is_param_mutated {
quote! { mut #param_ident: #ty }
} else {
quote! { #param_ident: #ty }
})
} else {
let rust_type = ctx
.annotation_aware_mapper
.map_type_with_annotations(¶m.ty, &func.annotations);
update_import_needs(ctx, &rust_type);
let ty = rust_type_to_syn(&rust_type)?;
Ok(if is_param_mutated {
quote! { mut #param_ident: #ty }
} else {
quote! { #param_ident: #ty }
})
}
}
fn apply_param_borrowing_strategy(
param_name: &str,
rust_type: &crate::type_mapper::RustType,
inferred: &crate::lifetime_analysis::InferredParam,
lifetime_result: &crate::lifetime_analysis::LifetimeResult,
ctx: &mut CodeGenContext,
) -> Result<syn::Type> {
let mut ty = rust_type_to_syn(rust_type)?;
if let Some(strategy) = lifetime_result.borrowing_strategies.get(param_name) {
match strategy {
crate::borrowing_context::BorrowingStrategy::UseCow { lifetime } => {
ctx.needs_cow = true;
let lt = syn::Lifetime::new(lifetime, proc_macro2::Span::call_site());
ty = parse_quote! { Cow<#lt, str> };
}
_ => {
if inferred.should_borrow {
ty = apply_borrowing_to_type(ty, rust_type, inferred)?;
}
}
}
} else {
if inferred.should_borrow {
ty = apply_borrowing_to_type(ty, rust_type, inferred)?;
}
}
Ok(ty)
}
fn apply_borrowing_to_type(
mut ty: syn::Type,
rust_type: &crate::type_mapper::RustType,
inferred: &crate::lifetime_analysis::InferredParam,
) -> Result<syn::Type> {
if matches!(rust_type, crate::type_mapper::RustType::String) {
if let Some(ref lifetime) = inferred.lifetime {
let lt = syn::Lifetime::new(lifetime.as_str(), proc_macro2::Span::call_site());
ty = if inferred.needs_mut {
parse_quote! { &#lt mut str }
} else {
parse_quote! { &#lt str }
};
} else {
ty = if inferred.needs_mut {
parse_quote! { &mut str }
} else {
parse_quote! { &str }
};
}
} else {
if let Some(ref lifetime) = inferred.lifetime {
let lt = syn::Lifetime::new(lifetime.as_str(), proc_macro2::Span::call_site());
ty = if inferred.needs_mut {
parse_quote! { &#lt mut #ty }
} else {
parse_quote! { &#lt #ty }
};
} else {
ty = if inferred.needs_mut {
parse_quote! { &mut #ty }
} else {
parse_quote! { &#ty }
};
}
}
Ok(ty)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum StringMethodReturnType {
Owned,
Borrowed,
}
fn classify_string_method(method_name: &str) -> StringMethodReturnType {
match method_name {
"upper" | "lower" | "strip" | "lstrip" | "rstrip" | "replace" | "format" | "title"
| "capitalize" | "swapcase" | "expandtabs" | "center" | "ljust" | "rjust" | "zfill" => {
StringMethodReturnType::Owned
}
"startswith" | "endswith" | "isalpha" | "isdigit" | "isalnum" | "isspace" | "islower"
| "isupper" | "istitle" | "isascii" | "isprintable" | "find" | "rfind" | "index"
| "rindex" | "count" => StringMethodReturnType::Borrowed,
_ => StringMethodReturnType::Owned,
}
}
fn contains_owned_string_method(expr: &HirExpr) -> bool {
match expr {
HirExpr::MethodCall { method, .. } => {
classify_string_method(method) == StringMethodReturnType::Owned
}
HirExpr::Binary { left, right, .. } => {
contains_owned_string_method(left) || contains_owned_string_method(right)
}
HirExpr::Unary { operand, .. } => contains_owned_string_method(operand),
HirExpr::IfExpr { body, orelse, .. } => {
contains_owned_string_method(body) || contains_owned_string_method(orelse)
}
HirExpr::Call { .. }
| HirExpr::Var(_)
| HirExpr::Literal(_)
| HirExpr::List(_)
| HirExpr::Dict(_)
| HirExpr::Tuple(_)
| HirExpr::Set(_)
| HirExpr::FrozenSet(_)
| HirExpr::Index { .. }
| HirExpr::Slice { .. }
| HirExpr::Attribute { .. }
| HirExpr::Borrow { .. }
| HirExpr::ListComp { .. }
| HirExpr::SetComp { .. }
| HirExpr::DictComp { .. }
| HirExpr::Lambda { .. }
| HirExpr::Await { .. }
| HirExpr::FString { .. }
| HirExpr::Yield { .. }
| HirExpr::SortByKey { .. }
| HirExpr::GeneratorExp { .. } => false,
}
}
fn function_returns_owned_string(func: &HirFunction) -> bool {
for stmt in &func.body {
if let HirStmt::Return(Some(expr)) = stmt {
if contains_owned_string_method(expr) {
return true;
}
}
}
false
}
pub(crate) fn return_type_expects_float(ty: &Type) -> bool {
match ty {
Type::Float => true,
Type::Optional(inner) => return_type_expects_float(inner),
Type::List(inner) => return_type_expects_float(inner),
Type::Tuple(types) => types.iter().any(return_type_expects_float),
_ => false,
}
}
#[inline]
pub(crate) fn codegen_return_type(
func: &HirFunction,
lifetime_result: &crate::lifetime_analysis::LifetimeResult,
ctx: &mut CodeGenContext,
) -> Result<(proc_macro2::TokenStream, crate::type_mapper::RustType, bool)> {
let mapped_ret_type = ctx
.annotation_aware_mapper
.map_return_type_with_annotations(&func.ret_type, &func.annotations);
let rust_ret_type = if let crate::type_mapper::RustType::Enum { name, .. } = &mapped_ret_type {
if name == "UnionType" {
if let Type::Union(types) = &func.ret_type {
let enum_name = ctx.process_union_type(types);
crate::type_mapper::RustType::Custom(enum_name)
} else {
mapped_ret_type
}
} else {
mapped_ret_type
}
} else {
mapped_ret_type
};
let rust_ret_type =
if matches!(func.ret_type, Type::String) && function_returns_owned_string(func) {
crate::type_mapper::RustType::String
} else {
rust_ret_type
};
update_import_needs(ctx, &rust_ret_type);
let can_fail = func.properties.can_fail;
let error_type_str = if can_fail && !func.properties.error_types.is_empty() {
if func.properties.error_types.len() == 1 {
func.properties.error_types[0].clone()
} else {
"Box<dyn std::error::Error>".to_string()
}
} else {
"Box<dyn std::error::Error>".to_string()
};
if error_type_str.contains("ZeroDivisionError") {
ctx.needs_zerodivisionerror = true;
}
if error_type_str.contains("IndexError") {
ctx.needs_indexerror = true;
}
let return_type = if matches!(rust_ret_type, crate::type_mapper::RustType::Unit) {
if can_fail {
let error_type: syn::Type = syn::parse_str(&error_type_str)
.unwrap_or_else(|_| parse_quote! { Box<dyn std::error::Error> });
quote! { -> Result<(), #error_type> }
} else {
quote! {}
}
} else {
let mut ty = rust_type_to_syn(&rust_ret_type)?;
let mut uses_cow_return = false;
for param in &func.params {
if let Some(strategy) = lifetime_result.borrowing_strategies.get(¶m.name) {
if matches!(
strategy,
crate::borrowing_context::BorrowingStrategy::UseCow { .. }
) {
if let Some(_usage) = lifetime_result.param_lifetimes.get(¶m.name) {
if matches!(func.ret_type, crate::hir::Type::String) {
uses_cow_return = true;
break;
}
}
}
}
}
if uses_cow_return {
ctx.needs_cow = true;
if let Some(ref return_lt) = lifetime_result.return_lifetime {
let lt = syn::Lifetime::new(return_lt.as_str(), proc_macro2::Span::call_site());
ty = parse_quote! { Cow<#lt, str> };
} else {
ty = parse_quote! { Cow<'static, str> };
}
} else {
let returns_owned_string =
matches!(func.ret_type, Type::String) && function_returns_owned_string(func);
if let Some(ref return_lt) = lifetime_result.return_lifetime {
if matches!(
rust_ret_type,
crate::type_mapper::RustType::Str { .. }
| crate::type_mapper::RustType::Reference { .. }
) && !returns_owned_string
{
let lt = syn::Lifetime::new(return_lt.as_str(), proc_macro2::Span::call_site());
match &rust_ret_type {
crate::type_mapper::RustType::Str { .. } => {
ty = parse_quote! { &#lt str };
}
crate::type_mapper::RustType::Reference { mutable, inner, .. } => {
let inner_ty = rust_type_to_syn(inner)?;
ty = if *mutable {
parse_quote! { &#lt mut #inner_ty }
} else {
parse_quote! { &#lt #inner_ty }
};
}
_ => {}
}
}
}
}
if can_fail {
let error_type: syn::Type = syn::parse_str(&error_type_str)
.unwrap_or_else(|_| parse_quote! { Box<dyn std::error::Error> });
quote! { -> Result<#ty, #error_type> }
} else {
quote! { -> #ty }
}
};
Ok((return_type, rust_ret_type, can_fail))
}
impl RustCodeGen for HirFunction {
fn to_rust_tokens(&self, ctx: &mut CodeGenContext) -> Result<proc_macro2::TokenStream> {
let name = syn::Ident::new(&self.name, proc_macro2::Span::call_site());
let mut generic_registry = crate::generic_inference::TypeVarRegistry::new();
let type_params = generic_registry.infer_function_generics(self)?;
let mut lifetime_inference = LifetimeInference::new();
let lifetime_result = lifetime_inference.analyze_function(self, ctx.type_mapper);
let generic_params = codegen_generic_params(&type_params, &lifetime_result.lifetime_params);
let where_clause = codegen_where_clause(&lifetime_result.lifetime_bounds);
let params = codegen_function_params(self, &lifetime_result, ctx)?;
let (return_type, rust_ret_type, can_fail) =
codegen_return_type(self, &lifetime_result, ctx)?;
let body_stmts = codegen_function_body(self, can_fail, ctx)?;
let attrs = codegen_function_attrs(&self.docstring, &self.properties);
let func_tokens = if self.properties.is_generator {
codegen_generator_function(
self,
&name,
&generic_params,
&where_clause,
¶ms,
&attrs,
&rust_ret_type,
ctx,
)?
} else if self.properties.is_async {
quote! {
#(#attrs)*
pub async fn #name #generic_params(#(#params),*) #return_type #where_clause {
#(#body_stmts)*
}
}
} else {
quote! {
#(#attrs)*
pub fn #name #generic_params(#(#params),*) #return_type #where_clause {
#(#body_stmts)*
}
}
};
Ok(func_tokens)
}
}