python_ast/ast/tree/
attribute.rs

1use proc_macro2::TokenStream;
2use pyo3::{Bound, PyAny, FromPyObject, PyResult, prelude::PyAnyMethods, types::PyTypeMethods};
3use quote::{format_ident, quote};
4
5use crate::{dump, CodeGen, CodeGenContext, ExprType, Node, PythonOptions, SymbolTableScopes};
6
7use serde::{Deserialize, Serialize};
8
9#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
10//#[pyo3(transparent)]
11pub struct Attribute {
12    value: Box<ExprType>,
13    attr: String,
14    ctx: String,
15}
16
17impl<'a> FromPyObject<'a> for Attribute {
18    fn extract_bound(ob: &Bound<'a, PyAny>) -> PyResult<Self> {
19        let value = ob.getattr("value").expect("Attribute.value");
20        let attr = ob.getattr("attr").expect("Attribute.attr");
21        let ctx = ob
22            .getattr("ctx")
23            .expect("getting attribute context")
24            .get_type()
25            .name()
26            .expect(
27                ob.error_message(
28                    "<unknown>",
29                    format!("extracting type name {:?} in attribute", dump(ob, None)),
30                )
31                .as_str(),
32            );
33        Ok(Attribute {
34            value: Box::new(value.extract().expect("Attribute.value")),
35            attr: attr.extract().expect("Attribute.attr"),
36            ctx: ctx.to_string(),
37        })
38    }
39}
40
41impl<'a> CodeGen for Attribute {
42    type Context = CodeGenContext;
43    type Options = PythonOptions;
44    type SymbolTable = SymbolTableScopes;
45
46    fn to_rust(
47        self,
48        ctx: Self::Context,
49        options: Self::Options,
50        symbols: Self::SymbolTable,
51    ) -> Result<TokenStream, Box<dyn std::error::Error>> {
52        let value_tokens = self.value.to_rust(ctx, options, symbols)?;
53        let value_str = value_tokens.to_string();
54        let attr = format_ident!("{}", self.attr);
55        
56        // Determine if this is a module access or a field/method access
57        // Module names are typically lowercase and match Python stdlib modules
58        let is_module_access = matches!(value_str.as_str(), 
59            "sys" | "os" | "subprocess" | "json" | "urllib" | "xml" | "asyncio" |
60            "os :: path" | "os::path" // for nested modules
61        );
62        
63        if is_module_access {
64            // Use :: for module access (Python's sys.executable becomes sys::executable)
65            // Special handling for LazyLock static variables that need dereferencing
66            let needs_deref = matches!((value_str.as_str(), self.attr.as_str()), 
67                ("sys", "executable") | ("sys", "argv") | ("os", "environ")
68            );
69            
70            if needs_deref {
71                // Wrap dereferenced values in parentheses to ensure correct precedence
72                // This prevents *sys::executable.to_string() and ensures (*sys::executable).to_string()
73                Ok(quote!((*#value_tokens::#attr)))
74            } else {
75                Ok(quote!(#value_tokens::#attr))
76            }
77        } else {
78            // Use . for field/method access (Python's obj.field becomes obj.field)
79            Ok(quote!(#value_tokens.#attr))
80        }
81    }
82}