Skip to main content

libperl_rs/
pad.rs

1//! Pad-name access — the lexical (`my` / `our`) variable names of a
2//! CV, read from its PADLIST's name list. Step 2's "lexical pad
3//! resolution" piece (`docs/plan/README.md` §4 Step 3.2), extracted
4//! from libperl-proto0's `eg/pad0.rs` / example `102_padname_type.rs`
5//! and perl-optree-analyzer's `raw.rs`.
6//!
7//! All accessors go through the macrogen-emitted official API
8//! (`PadlistNAMES`, `PadnamelistMAX` / `PadnamelistARRAY`,
9//! `PadnamePV` / `PadnameLEN` / `PadnameTYPE`). On perl 5.28/5.30
10//! those `Padname*` accessors come from the hand-written compat block
11//! in `libperl-sys/src/perl_core.rs` (macrogen still suppresses them
12//! there); the call sites here are identical either way.
13//!
14//! Entry point: [`Cv::pad_names`](crate::Cv::pad_names).
15
16use std::ptr::NonNull;
17
18use libperl_sys::{PADLIST, PADNAME, PADNAMELIST};
19
20/// Non-null pointer to a Perl `PADNAME` — one lexical's name slot.
21/// Non-owning, like the other newtypes.
22#[derive(Clone, Copy)]
23#[repr(transparent)]
24pub struct PadName(NonNull<PADNAME>);
25
26impl PadName {
27    /// Wrap a raw PADNAME pointer, returning `None` on null input.
28    #[inline]
29    pub fn from_raw(p: *const PADNAME) -> Option<Self> {
30        NonNull::new(p as *mut PADNAME).map(PadName)
31    }
32
33    /// Raw pointer for FFI calls.
34    #[inline]
35    pub fn as_ptr(&self) -> *mut PADNAME {
36        self.0.as_ptr()
37    }
38
39    /// The lexical's name including sigil (`"$x"`, `"@args"`, ...),
40    /// or `None` for unnamed slots (targets, sub-op temporaries).
41    pub fn pv(&self) -> Option<String> {
42        let pv = unsafe { libperl_sys::PadnamePV(self.as_ptr()) };
43        if pv.is_null() {
44            return None;
45        }
46        let len = unsafe { libperl_sys::PadnameLEN(self.as_ptr()) };
47        let bytes = unsafe { std::slice::from_raw_parts(pv as *const u8, len as usize) };
48        Some(String::from_utf8_lossy(bytes).into_owned())
49    }
50
51    /// For `my Foo $x`-style typed lexicals: the type stash's name
52    /// (`"Foo"`). `None` for untyped lexicals.
53    pub fn type_stash_name(&self) -> Option<String> {
54        let stash = unsafe { libperl_sys::PadnameTYPE(self.as_ptr()) };
55        if stash.is_null() {
56            return None;
57        }
58        let p = unsafe { libperl_sys::HvNAME(stash) };
59        if p.is_null() {
60            None
61        } else {
62            Some(
63                unsafe { std::ffi::CStr::from_ptr(p) }
64                    .to_string_lossy()
65                    .into_owned(),
66            )
67        }
68    }
69}
70
71/// Iterator over a CV's pad-name slots, yielded by
72/// [`Cv::pad_names`](crate::Cv::pad_names). Each item corresponds to
73/// one pad offset (starting at 0); `None` items are allocated but
74/// nameless slots. Pair with `.enumerate()` when the pad offsets
75/// matter.
76pub struct PadNames {
77    arr: *mut *mut PADNAME,
78    ix: isize,
79    max: isize,
80}
81
82impl PadNames {
83    /// Build from a CV's PADLIST pointer (null-safe: a null padlist —
84    /// e.g. an XSUB's — yields an empty iterator).
85    pub(crate) fn from_padlist(pl: *const PADLIST) -> PadNames {
86        let empty = PadNames {
87            arr: std::ptr::null_mut(),
88            ix: 0,
89            max: -1,
90        };
91        if pl.is_null() {
92            return empty;
93        }
94        let pnl: *const PADNAMELIST = unsafe { libperl_sys::PadlistNAMES(pl) };
95        if pnl.is_null() {
96            return empty;
97        }
98        PadNames {
99            arr: unsafe { libperl_sys::PadnamelistARRAY(pnl) },
100            ix: 0,
101            // `PadnamelistMAX` is the last used index (xpadnl_fill),
102            // -1 when empty — same convention as AvFILL.
103            max: unsafe { libperl_sys::PadnamelistMAX(pnl) as isize },
104        }
105    }
106}
107
108impl Iterator for PadNames {
109    type Item = Option<PadName>;
110
111    fn next(&mut self) -> Option<Self::Item> {
112        if self.ix > self.max || self.arr.is_null() {
113            return None;
114        }
115        let p = unsafe { *self.arr.offset(self.ix) };
116        self.ix += 1;
117        Some(PadName::from_raw(p))
118    }
119
120    fn size_hint(&self) -> (usize, Option<usize>) {
121        let r = (self.max + 1 - self.ix).max(0) as usize;
122        (r, Some(r))
123    }
124}