Skip to main content

rustpython_vm/function/
doctext.rs

1//! A docstring is a static literal, a span of the compressed blob, or both.
2//! `len == 0` means there is no database body. `offset == u32::MAX` is an
3//! explicit empty entry.
4
5#[cfg(feature = "doc")]
6use std::sync::OnceLock;
7
8/// Static text plus an optional span of the compressed doc blob.
9///
10/// `text` is a plain docstring, a full `name(sig)\n--\n\nbody` literal, or only
11/// the signature prefix when `len` is the database body.
12#[derive(Clone, Copy, Debug)]
13pub struct ItemDoc {
14    pub text: Option<&'static str>,
15    pub offset: u32,
16    pub len: u32,
17}
18
19impl ItemDoc {
20    pub const NONE: Self = Self {
21        text: None,
22        offset: 0,
23        len: 0,
24    };
25
26    /// An explicit empty docstring. `offset == u32::MAX` distinguishes it from
27    /// [`NONE`](Self::NONE), which means the item has no docstring at all.
28    pub const EMPTY: Self = Self {
29        text: None,
30        offset: u32::MAX,
31        len: 0,
32    };
33
34    #[must_use]
35    pub const fn static_text(text: &'static str) -> Self {
36        Self {
37            text: Some(text),
38            offset: 0,
39            len: 0,
40        }
41    }
42
43    #[must_use]
44    pub const fn is_db(self) -> bool {
45        self.len != 0
46    }
47
48    /// Database entry `key`. Evaluate it in a `const` so the lookup table is
49    /// not linked into the binary.
50    #[must_use]
51    pub const fn db(key: &str) -> Self {
52        #[cfg(feature = "doc")]
53        if let Some(doc) = rustpython_doc::get(key) {
54            return Self {
55                text: None,
56                offset: doc.offset,
57                len: doc.len,
58            };
59        }
60        #[cfg(not(feature = "doc"))]
61        let _ = key;
62        Self::NONE
63    }
64}
65
66impl Default for ItemDoc {
67    fn default() -> Self {
68        Self::NONE
69    }
70}
71
72#[inline(never)]
73#[must_use]
74pub fn db_doc(offset: u32, len: u32) -> Option<&'static str> {
75    if len == 0 {
76        return None;
77    }
78    db_slice(offset, len)
79}
80
81#[inline(never)]
82#[must_use]
83pub fn plain_doc(doc: ItemDoc) -> Option<&'static str> {
84    if doc.offset == u32::MAX && doc.len == 0 {
85        return Some("");
86    }
87    if doc.len != 0 {
88        return db_doc(doc.offset, doc.len);
89    }
90    doc.text.filter(|text| !text.is_empty())
91}
92
93#[cfg(feature = "doc")]
94#[inline(never)]
95fn db_slice(offset: u32, len: u32) -> Option<&'static str> {
96    let text = docs();
97    let start = offset as usize;
98    let end = start.checked_add(len as usize)?;
99    text.get(start..end)
100}
101
102#[cfg(not(feature = "doc"))]
103fn db_slice(_offset: u32, _len: u32) -> Option<&'static str> {
104    None
105}
106
107#[cfg(feature = "doc")]
108#[inline(never)]
109fn docs() -> &'static str {
110    static TEXT: OnceLock<Box<str>> = OnceLock::new();
111    TEXT.get_or_init(|| {
112        let bytes = match xz::decode_all(rustpython_doc::BLOB) {
113            Ok(bytes) => bytes,
114            Err(_) => panic!("doc blob"),
115        };
116        match String::from_utf8(bytes) {
117            Ok(text) => text.into_boxed_str(),
118            Err(_) => panic!("doc blob"),
119        }
120    })
121    .as_ref()
122}
123
124#[cfg(test)]
125mod tests {
126    use super::{ItemDoc, plain_doc};
127
128    #[test]
129    fn explicit_empty_doc_is_empty_string() {
130        assert_eq!(plain_doc(ItemDoc::EMPTY), Some(""));
131        assert_eq!(plain_doc(ItemDoc::NONE), None);
132        assert_eq!(plain_doc(ItemDoc::static_text("")), None);
133        assert_eq!(plain_doc(ItemDoc::static_text("a")), Some("a"));
134    }
135}