pyo3_stub_gen/generate/
parameters.rs

1use crate::{
2    generate::Import,
3    stub_type::{ImportRef, ModuleRef},
4    type_info::{ParameterDefault as ParameterDefaultInfo, ParameterInfo, ParameterKind},
5    TypeInfo,
6};
7use std::{collections::HashSet, fmt};
8
9/// Default value of a parameter (runtime version)
10#[derive(Debug, Clone, PartialEq)]
11pub enum ParameterDefault {
12    /// No default value
13    None,
14    /// Default value expression with optional module qualification info
15    Expr {
16        /// The default value expression (without module prefix)
17        value: String,
18        /// Source module of the type referenced in the default value
19        source_module: Option<ModuleRef>,
20    },
21}
22
23/// A single parameter in a Python function/method signature
24///
25/// This struct represents a parameter at runtime during stub generation.
26#[derive(Debug, Clone, PartialEq)]
27pub struct Parameter {
28    /// Parameter name
29    pub name: &'static str,
30    /// Parameter kind (positional-only, keyword-only, etc.)
31    pub kind: ParameterKind,
32    /// Type information
33    pub type_info: TypeInfo,
34    /// Default value
35    pub default: ParameterDefault,
36}
37
38impl Import for Parameter {
39    fn import(&self) -> HashSet<ImportRef> {
40        self.type_info.import.clone()
41    }
42}
43
44impl From<&ParameterInfo> for Parameter {
45    fn from(info: &ParameterInfo) -> Self {
46        Self {
47            name: info.name,
48            kind: info.kind,
49            type_info: (info.type_info)(),
50            default: match &info.default {
51                ParameterDefaultInfo::None => ParameterDefault::None,
52                ParameterDefaultInfo::Expr {
53                    value,
54                    source_module,
55                } => ParameterDefault::Expr {
56                    value: value(),
57                    source_module: source_module.and_then(|f| f()),
58                },
59            },
60        }
61    }
62}
63
64impl fmt::Display for Parameter {
65    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
66        match self.kind {
67            ParameterKind::VarPositional => {
68                write!(f, "*{}: {}", self.name, self.type_info)
69            }
70            ParameterKind::VarKeyword => {
71                write!(f, "**{}: {}", self.name, self.type_info)
72            }
73            _ => {
74                write!(f, "{}: {}", self.name, self.type_info)?;
75                match &self.default {
76                    ParameterDefault::None => Ok(()),
77                    ParameterDefault::Expr { value, .. } => write!(f, " = {}", value),
78                }
79            }
80        }
81    }
82}
83
84/// Container for parameters in a Python function/method signature
85///
86/// This struct organizes parameters into sections according to Python's signature syntax,
87/// ensuring proper ordering and placement of delimiters (`/` and `*`).
88#[derive(Debug, Clone, PartialEq)]
89pub struct Parameters {
90    /// Positional-only parameters (before `/`)
91    pub positional_only: Vec<Parameter>,
92    /// Positional or keyword parameters
93    pub positional_or_keyword: Vec<Parameter>,
94    /// Keyword-only parameters (after `*`)
95    pub keyword_only: Vec<Parameter>,
96    /// Variable positional parameter (`*args`)
97    pub varargs: Option<Parameter>,
98    /// Variable keyword parameter (`**kwargs`)
99    pub varkw: Option<Parameter>,
100}
101
102impl Parameters {
103    /// Create an empty Parameters container
104    pub fn new() -> Self {
105        Self {
106            positional_only: Vec::new(),
107            positional_or_keyword: Vec::new(),
108            keyword_only: Vec::new(),
109            varargs: None,
110            varkw: None,
111        }
112    }
113
114    /// Build Parameters from a slice of ParameterInfo
115    pub fn from_infos(infos: &[ParameterInfo]) -> Self {
116        let mut params = Self::new();
117
118        for info in infos {
119            let param = Parameter::from(info);
120            match param.kind {
121                ParameterKind::PositionalOnly => params.positional_only.push(param),
122                ParameterKind::PositionalOrKeyword => params.positional_or_keyword.push(param),
123                ParameterKind::KeywordOnly => params.keyword_only.push(param),
124                ParameterKind::VarPositional => params.varargs = Some(param),
125                ParameterKind::VarKeyword => params.varkw = Some(param),
126            }
127        }
128
129        params
130    }
131
132    /// Iterate over all parameters in signature order
133    pub fn iter_entries(&self) -> impl Iterator<Item = &Parameter> {
134        self.positional_only
135            .iter()
136            .chain(self.positional_or_keyword.iter())
137            .chain(self.varargs.iter())
138            .chain(self.keyword_only.iter())
139            .chain(self.varkw.iter())
140    }
141
142    /// Check if there are no parameters at all
143    pub fn is_empty(&self) -> bool {
144        self.positional_only.is_empty()
145            && self.positional_or_keyword.is_empty()
146            && self.keyword_only.is_empty()
147            && self.varargs.is_none()
148            && self.varkw.is_none()
149    }
150}
151
152impl Default for Parameters {
153    fn default() -> Self {
154        Self::new()
155    }
156}
157
158impl Import for Parameters {
159    fn import(&self) -> HashSet<ImportRef> {
160        self.iter_entries().flat_map(|p| p.import()).collect()
161    }
162}
163
164impl fmt::Display for Parameters {
165    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
166        let mut parts = Vec::new();
167
168        // Positional-only parameters
169        for param in &self.positional_only {
170            parts.push(param.to_string());
171        }
172
173        // Insert `/` delimiter if there are positional-only parameters
174        if !self.positional_only.is_empty() {
175            parts.push("/".to_string());
176        }
177
178        // Positional or keyword parameters
179        for param in &self.positional_or_keyword {
180            parts.push(param.to_string());
181        }
182
183        // Variable positional parameter or bare `*` for keyword-only
184        if let Some(varargs) = &self.varargs {
185            parts.push(varargs.to_string());
186        } else if !self.keyword_only.is_empty() {
187            // Need bare `*` to indicate keyword-only parameters follow
188            parts.push("*".to_string());
189        }
190
191        // Keyword-only parameters
192        for param in &self.keyword_only {
193            parts.push(param.to_string());
194        }
195
196        // Variable keyword parameter
197        if let Some(varkw) = &self.varkw {
198            parts.push(varkw.to_string());
199        }
200
201        write!(f, "{}", parts.join(", "))
202    }
203}
204
205impl Parameters {
206    /// Format parameters with module-qualified type names
207    ///
208    /// This method uses the target module context to qualify type identifiers
209    /// within compound type expressions based on their source modules.
210    pub fn fmt_for_module(&self, target_module: &str) -> String {
211        let mut parts = Vec::new();
212
213        // Positional-only parameters
214        for param in &self.positional_only {
215            parts.push(Self::format_param(param, target_module));
216        }
217
218        // Insert `/` delimiter if there are positional-only parameters
219        if !self.positional_only.is_empty() {
220            parts.push("/".to_string());
221        }
222
223        // Positional or keyword parameters
224        for param in &self.positional_or_keyword {
225            parts.push(Self::format_param(param, target_module));
226        }
227
228        // Variable positional parameter or bare `*` for keyword-only
229        if let Some(varargs) = &self.varargs {
230            parts.push(Self::format_param(varargs, target_module));
231        } else if !self.keyword_only.is_empty() {
232            // Need bare `*` to indicate keyword-only parameters follow
233            parts.push("*".to_string());
234        }
235
236        // Keyword-only parameters
237        for param in &self.keyword_only {
238            parts.push(Self::format_param(param, target_module));
239        }
240
241        // Variable keyword parameter
242        if let Some(varkw) = &self.varkw {
243            parts.push(Self::format_param(varkw, target_module));
244        }
245
246        parts.join(", ")
247    }
248
249    /// Format a single parameter with qualified type names
250    fn format_param(param: &Parameter, target_module: &str) -> String {
251        let qualified_type = param.type_info.qualified_for_module(target_module);
252        match param.kind {
253            ParameterKind::VarPositional => format!("*{}: {}", param.name, qualified_type),
254            ParameterKind::VarKeyword => format!("**{}: {}", param.name, qualified_type),
255            _ => {
256                let base = format!("{}: {}", param.name, qualified_type);
257                match &param.default {
258                    ParameterDefault::None => base,
259                    ParameterDefault::Expr {
260                        value,
261                        source_module,
262                    } => {
263                        let qualified_expr =
264                            qualify_default_value(value, source_module.as_ref(), target_module);
265                        format!("{} = {}", base, qualified_expr)
266                    }
267                }
268            }
269        }
270    }
271}
272
273/// Qualify a default value expression based on target module
274///
275/// This function adds module prefixes to default value expressions
276/// when the source module differs from the target module.
277fn qualify_default_value(
278    value: &str,
279    source_module: Option<&ModuleRef>,
280    target_module: &str,
281) -> String {
282    let Some(module_ref) = source_module else {
283        return value.to_string();
284    };
285    let Some(module_name) = module_ref.get() else {
286        return value.to_string();
287    };
288
289    // Check if same module using exact match only
290    // This avoids incorrectly treating pkg.a._core and pkg.b._core as the same module
291    if module_name == target_module {
292        value.to_string()
293    } else {
294        // Use the last component of the module path for qualification
295        // This matches how imports are typically structured (e.g., `from pkg import _core`)
296        let module_component = module_name.rsplit('.').next().unwrap_or(module_name);
297        format!("{}.{}", module_component, value)
298    }
299}
300
301#[cfg(test)]
302mod tests {
303    use super::*;
304
305    #[test]
306    fn test_positional_only() {
307        let params = Parameters {
308            positional_only: vec![
309                Parameter {
310                    name: "x",
311                    kind: ParameterKind::PositionalOnly,
312                    type_info: TypeInfo::builtin("int"),
313                    default: ParameterDefault::None,
314                },
315                Parameter {
316                    name: "y",
317                    kind: ParameterKind::PositionalOnly,
318                    type_info: TypeInfo::builtin("int"),
319                    default: ParameterDefault::None,
320                },
321            ],
322            ..Default::default()
323        };
324
325        assert_eq!(params.to_string(), "x: builtins.int, y: builtins.int, /");
326    }
327
328    #[test]
329    fn test_keyword_only() {
330        let params = Parameters {
331            keyword_only: vec![Parameter {
332                name: "kw",
333                kind: ParameterKind::KeywordOnly,
334                type_info: TypeInfo::builtin("str"),
335                default: ParameterDefault::Expr {
336                    value: "None".to_string(),
337                    source_module: None,
338                },
339            }],
340            ..Default::default()
341        };
342
343        assert_eq!(params.to_string(), "*, kw: builtins.str = None");
344    }
345
346    #[test]
347    fn test_mixed_with_defaults() {
348        let params = Parameters {
349            positional_only: vec![Parameter {
350                name: "token",
351                kind: ParameterKind::PositionalOnly,
352                type_info: TypeInfo::builtin("str"),
353                default: ParameterDefault::None,
354            }],
355            keyword_only: vec![
356                Parameter {
357                    name: "retries",
358                    kind: ParameterKind::KeywordOnly,
359                    type_info: TypeInfo::builtin("int"),
360                    default: ParameterDefault::Expr {
361                        value: "3".to_string(),
362                        source_module: None,
363                    },
364                },
365                Parameter {
366                    name: "timeout",
367                    kind: ParameterKind::KeywordOnly,
368                    type_info: TypeInfo::builtin("float"),
369                    default: ParameterDefault::None,
370                },
371            ],
372            ..Default::default()
373        };
374
375        assert_eq!(
376            params.to_string(),
377            "token: builtins.str, /, *, retries: builtins.int = 3, timeout: builtins.float"
378        );
379    }
380
381    #[test]
382    fn test_varargs_kwargs() {
383        let params = Parameters {
384            varargs: Some(Parameter {
385                name: "args",
386                kind: ParameterKind::VarPositional,
387                type_info: TypeInfo::builtin("str"),
388                default: ParameterDefault::None,
389            }),
390            varkw: Some(Parameter {
391                name: "kwargs",
392                kind: ParameterKind::VarKeyword,
393                type_info: TypeInfo::any(),
394                default: ParameterDefault::None,
395            }),
396            ..Default::default()
397        };
398
399        assert_eq!(
400            params.to_string(),
401            "*args: builtins.str, **kwargs: typing.Any"
402        );
403    }
404
405    #[test]
406    fn test_qualify_default_value_same_module() {
407        // Same module: no qualification needed
408        let module_ref = ModuleRef::Named("pkg._core".to_string());
409        let result = qualify_default_value("C.C1", Some(&module_ref), "pkg._core");
410        assert_eq!(result, "C.C1");
411    }
412
413    #[test]
414    fn test_qualify_default_value_different_module() {
415        // Different module: add qualification
416        let module_ref = ModuleRef::Named("pkg._core".to_string());
417        let result = qualify_default_value("C.C1", Some(&module_ref), "pkg");
418        assert_eq!(result, "_core.C.C1");
419    }
420
421    #[test]
422    fn test_qualify_default_value_different_submodules() {
423        // P2 fix: pkg.a._core and pkg.b._core should be treated as different modules
424        // (previously suffix matching would incorrectly treat them as same)
425        let module_ref = ModuleRef::Named("pkg.a._core".to_string());
426        let result = qualify_default_value("C.C1", Some(&module_ref), "pkg.b._core");
427        assert_eq!(result, "_core.C.C1");
428    }
429
430    #[test]
431    fn test_qualify_default_value_no_source_module() {
432        // No source module: return value unchanged
433        let result = qualify_default_value("None", None, "pkg._core");
434        assert_eq!(result, "None");
435    }
436}