python_ast/ast/tree/class_def.rs
1//! A lot of languages, Python included, have a concept of a class, which combines the definition of a data type with
2//! an interface. In dynamic languages like Python, the class itself is a memory object, that can be permutated at runtime,
3//! however, this is probably usually a bad idea. Classes can contain:
4//! 1. Methods (special functions)
5//! 2. properties (attributes of the data element)
6//! 3. Base classes (for inheritace)
7//! 4. static data
8//! 5. Additional classes
9//!
10//! There is one construct in Rust that can emcompass all of these things: a module. So, we use modules to model classes
11//! following these rules:
12//! 1. The module is given the name of the class. Contrary to other Rust modules, this is typically SnakeCase.
13//! 2. The main data type defined by the class is a struct inside the module, and called Data.
14//! 3. The Data struct can take two forms:
15//! a. If the properties of the class can be fully inferred, Data will be a simple struct and the attributes will be defined as fields of the struct.
16//! b. If the properties of the class cannot be fully inferred, such as if the class is accessed as a dictionary, Data will be a HashMap<String, _>,
17//! and the values will be accessed through it.
18//! 4. Static data will be declared with lazy_static inside the module.
19//! 5. Additional classes will be nested inside the module, and therefore they appear as modules inside a module.
20//! 6. Each class also contains a trait, named Cls, which is used in inheritance.
21//! 7. Each method of the class in Python will be translated to have a prototype in Cls. If it is possible to implement the method as a default method,
22//! it will be, otherwise (if the method refers to attributes of the class), a prototype will be added to Cls, and the implementation will be done inside
23//! an impl Cls for Data block.
24//! 8. Cls will implement Clone, Default.
25
26use proc_macro2::TokenStream;
27use pyo3::FromPyObject;
28use quote::{format_ident, quote};
29
30use crate::{
31 CodeGen, CodeGenContext, ExprType, Name, PythonOptions, Statement, StatementType,
32 SymbolTableNode, SymbolTableScopes,
33};
34
35use log::debug;
36
37use serde::{Deserialize, Serialize};
38
39#[derive(Clone, Debug, Default, FromPyObject, Serialize, Deserialize, PartialEq)]
40pub struct ClassDef {
41 pub name: String,
42 pub bases: Vec<Name>,
43 pub keywords: Vec<String>,
44 pub body: Vec<Statement>,
45}
46
47impl CodeGen for ClassDef {
48 type Context = CodeGenContext;
49 type Options = PythonOptions;
50 type SymbolTable = SymbolTableScopes;
51
52 fn find_symbols(self, symbols: Self::SymbolTable) -> Self::SymbolTable {
53 let mut symbols = symbols;
54 symbols.insert(self.name.clone(), SymbolTableNode::ClassDef(self.clone()));
55 symbols
56 }
57
58 fn to_rust(
59 self,
60 _ctx: Self::Context,
61 options: Self::Options,
62 symbols: Self::SymbolTable,
63 ) -> Result<TokenStream, Box<dyn std::error::Error>> {
64 let mut streams = TokenStream::new();
65 let class_name = format_ident!("{}", self.name);
66
67 // The Python convention is that functions that begin with a single underscore,
68 // it's private. Otherwise, it's public. We formalize that by default.
69 let visibility = if self.name.starts_with("_") && !self.name.starts_with("__") {
70 format_ident!("")
71 } else if self.name.starts_with("__") && self.name.ends_with("__") {
72 format_ident!("pub(crate)")
73 } else {
74 format_ident!("pub")
75 };
76
77 // bases will be empty if there are no base classes, which prevents any base traits
78 // being added, and also prevents the : from being emitted.
79 let mut bases = TokenStream::new();
80 if self.bases.len() > 0 {
81 bases.extend(quote!(:));
82 let base_name = format_ident!("{}", self.bases[0].id);
83 bases.extend(quote!(#base_name::Cls));
84 for base in &self.bases[1..] {
85 bases.extend(quote!(+));
86 let base_name = format_ident!("{}", base.id);
87 bases.extend(quote!(#base_name));
88 }
89 }
90
91 for s in self.body.clone() {
92 streams.extend(
93 s.clone()
94 .to_rust(CodeGenContext::Class, options.clone(), symbols.clone())
95 .expect(format!("Failed to parse statement {:?}", s).as_str()),
96 );
97 }
98
99 let docstring = if let Some(d) = self.get_docstring() {
100 format!("{}", d)
101 } else {
102 "".to_string()
103 };
104
105 let class = quote! {
106 #[doc #docstring]
107 #visibility mod #class_name {
108 use super::*;
109 #visibility trait Cls #bases {
110 #streams
111 }
112 #[derive(Clone, Default)]
113 #visibility struct Data {
114
115 }
116 impl Cls for Data {}
117 }
118 };
119 debug!("class: {}", class);
120 Ok(class)
121 }
122
123 fn get_docstring(&self) -> Option<String> {
124 let expr = self.body[0].clone();
125 match expr.statement {
126 StatementType::Expr(e) => match e.value {
127 ExprType::Constant(c) => Some(c.to_string()),
128 _ => None,
129 },
130 _ => None,
131 }
132 }
133}