Skip to main content

rustpython_vm/function/
signature.rs

1//! Compile-time text signatures for native functions.
2//!
3//! The internal doc is `name(params)\n--\n\ndoc`, built in a const context from
4//! [`FromArgs`] metadata. Nothing here allocates.
5
6use super::argument::FromArgs;
7
8#[derive(Clone, Copy, Debug)]
9pub enum ParamKind {
10    PositionalOnly,
11    PositionalOrKeyword,
12    KeywordOnly,
13    VarPositional,
14    VarKeyword,
15    /// Parameters contributed by a nested [`FromArgs`] type.
16    /// `None` contributes nothing.
17    Flatten(Option<&'static [Param]>),
18}
19
20/// Python source for a parameter default, chosen so it can be rendered in a const context.
21#[derive(Clone, Copy, Debug)]
22pub enum DefaultRepr {
23    None,
24    Bool(bool),
25    Int(i128),
26    Float(f64),
27    Str(&'static str),
28    Bytes(&'static [u8]),
29    /// Verbatim Python source from `py_default`.
30    Raw(&'static str),
31    /// The argument may be omitted, and that state is not a Python value.
32    Unrepresentable,
33}
34
35#[derive(Clone, Copy, Debug)]
36pub struct Param {
37    pub name: &'static str,
38    pub kind: ParamKind,
39    pub default: Option<DefaultRepr>,
40}
41
42impl Param {
43    #[must_use]
44    pub const fn positional_only(name: &'static str) -> Self {
45        Self {
46            name,
47            kind: ParamKind::PositionalOnly,
48            default: None,
49        }
50    }
51
52    #[must_use]
53    pub const fn positional_or_keyword(name: &'static str) -> Self {
54        Self {
55            name,
56            kind: ParamKind::PositionalOrKeyword,
57            default: None,
58        }
59    }
60
61    #[must_use]
62    pub const fn keyword_only(name: &'static str, default: Option<DefaultRepr>) -> Self {
63        Self {
64            name,
65            kind: ParamKind::KeywordOnly,
66            default,
67        }
68    }
69
70    #[must_use]
71    pub const fn var_positional(name: &'static str) -> Self {
72        Self {
73            name,
74            kind: ParamKind::VarPositional,
75            default: None,
76        }
77    }
78
79    #[must_use]
80    pub const fn var_keyword(name: &'static str) -> Self {
81        Self {
82            name,
83            kind: ParamKind::VarKeyword,
84            default: None,
85        }
86    }
87
88    #[must_use]
89    pub const fn flatten(params: Option<&'static [Self]>) -> Self {
90        Self {
91            name: "",
92            kind: ParamKind::Flatten(params),
93            default: None,
94        }
95    }
96}
97
98/// One Rust argument of a native function.
99///
100/// `params == None`: one positional-only parameter named [`name`](Self::name).
101/// `Some(ps)`: the type supplies `ps` and `name` is ignored.
102/// `Some(&[])`: the argument contributes nothing.
103#[derive(Clone, Copy, Debug)]
104pub struct SigArg {
105    pub name: &'static str,
106    pub params: Option<&'static [Param]>,
107}
108
109impl SigArg {
110    #[must_use]
111    pub const fn from_arg<T: FromArgs>(name: &'static str) -> Self {
112        Self {
113            name,
114            params: T::PARAMS,
115        }
116    }
117
118    /// `$self` / `$type` receiver marker. Renders as one positional-only parameter.
119    #[must_use]
120    pub const fn marker(name: &'static str) -> Self {
121        Self { name, params: None }
122    }
123
124    /// Parameters supplied by the argument a method binds as its receiver.
125    /// `None` metadata contributes nothing, so this never looks nameless.
126    #[must_use]
127    pub const fn implicit<T: FromArgs>() -> Self {
128        Self {
129            name: "",
130            params: Some(match T::PARAMS {
131                Some(ps) => ps,
132                None => &[],
133            }),
134        }
135    }
136}
137
138const fn name_eq(name: &str, bytes: &[u8]) -> bool {
139    let got = name.as_bytes();
140    if got.len() != bytes.len() {
141        return false;
142    }
143    let mut i = 0;
144    while i < got.len() {
145        if got[i] != bytes[i] {
146            return false;
147        }
148        i += 1;
149    }
150    true
151}
152
153/// `FuncArgs` contributes `*args, **kwargs`, which is not a callable's signature.
154#[must_use]
155const fn is_bare_funcargs(params: &[Param]) -> bool {
156    params.len() == 2
157        && matches!(params[0].kind, ParamKind::VarPositional)
158        && name_eq(params[0].name, b"args")
159        && matches!(params[1].kind, ParamKind::VarKeyword)
160        && name_eq(params[1].name, b"kwargs")
161}
162
163const fn params_representable(params: &[Param]) -> bool {
164    let mut i = 0;
165    while i < params.len() {
166        if let Some(DefaultRepr::Unrepresentable) = params[i].default {
167            return false;
168        }
169        if let ParamKind::Flatten(Some(inner)) = params[i].kind
170            && !params_representable(inner)
171        {
172            return false;
173        }
174        i += 1;
175    }
176    true
177}
178
179/// `None`, a bare `FuncArgs`, and a default of `<unrepresentable>` contribute
180/// no signature. `Some(&[])` is `()`.
181#[must_use]
182pub const fn real_signature(params: Option<&[Param]>) -> Option<&[Param]> {
183    match params {
184        Some(ps) if !is_bare_funcargs(ps) && params_representable(ps) => Some(ps),
185        _ => None,
186    }
187}
188
189/// Prefer `preferred` when it is a real signature, otherwise `alternate`.
190#[must_use]
191pub const fn choose_class_params<'a>(
192    preferred: Option<&'a [Param]>,
193    alternate: Option<&'a [Param]>,
194) -> Option<&'a [Param]> {
195    match real_signature(preferred) {
196        Some(ps) => Some(ps),
197        None => real_signature(alternate),
198    }
199}
200
201/// False when an argument is a destructured pattern whose type has no [`FromArgs::PARAMS`].
202#[must_use]
203pub const fn has_signature(args: &[SigArg]) -> bool {
204    let mut i = 0;
205    while i < args.len() {
206        if args[i].params.is_none() && args[i].name.is_empty() {
207            return false;
208        }
209        i += 1;
210    }
211    true
212}
213
214#[must_use]
215pub const fn signature_prefix_len(name: &str, args: &[SigArg]) -> usize {
216    write_signature_prefix(&mut [], name, args)
217}
218
219#[must_use]
220pub const fn signature_prefix_bytes<const N: usize>(name: &str, args: &[SigArg]) -> [u8; N] {
221    let mut buf = [0u8; N];
222    let written = write_signature_prefix(&mut buf, name, args);
223    assert!(written == N);
224    buf
225}
226
227#[must_use]
228pub const fn internal_doc_len(name: &str, args: &[SigArg], doc: &str) -> usize {
229    write_internal_doc(&mut [], name, args, doc)
230}
231
232#[must_use]
233pub const fn internal_doc_bytes<const N: usize>(name: &str, args: &[SigArg], doc: &str) -> [u8; N] {
234    let mut buf = [0u8; N];
235    let written = write_internal_doc(&mut buf, name, args, doc);
236    assert!(written == N);
237    buf
238}
239
240struct St {
241    n: usize,
242    emitted: bool,
243    po_left: usize,
244    var_pos_seen: bool,
245    star_emitted: bool,
246}
247
248const fn write_signature_prefix(buf: &mut [u8], name: &str, args: &[SigArg]) -> usize {
249    let mut n = put_str(buf, 0, name);
250    n = put_byte(buf, n, b'(');
251    let st = write_args(
252        buf,
253        St {
254            n,
255            emitted: false,
256            po_left: count_po_args(args),
257            var_pos_seen: false,
258            star_emitted: false,
259        },
260        args,
261    );
262    put_str(buf, st.n, ")\n--\n\n")
263}
264
265const fn write_internal_doc(buf: &mut [u8], name: &str, args: &[SigArg], doc: &str) -> usize {
266    let n = write_signature_prefix(buf, name, args);
267    put_str(buf, n, doc)
268}
269
270const fn put(buf: &mut [u8], i: usize, bytes: &[u8]) -> usize {
271    let mut k = 0;
272    while k < bytes.len() {
273        let at = i + k;
274        if at < buf.len() {
275            buf[at] = bytes[k];
276        }
277        k += 1;
278    }
279    i + bytes.len()
280}
281
282const fn put_str(buf: &mut [u8], i: usize, s: &str) -> usize {
283    put(buf, i, s.as_bytes())
284}
285
286const fn put_byte(buf: &mut [u8], i: usize, b: u8) -> usize {
287    put(buf, i, &[b])
288}
289
290const fn count_po_params(params: &[Param]) -> usize {
291    let mut n = 0;
292    let mut i = 0;
293    while i < params.len() {
294        match params[i].kind {
295            ParamKind::PositionalOnly => n += 1,
296            ParamKind::Flatten(Some(inner)) => n += count_po_params(inner),
297            _ => {}
298        }
299        i += 1;
300    }
301    n
302}
303
304const fn count_po_args(args: &[SigArg]) -> usize {
305    let mut n = 0;
306    let mut i = 0;
307    while i < args.len() {
308        match args[i].params {
309            None => n += 1,
310            Some(ps) => n += count_po_params(ps),
311        }
312        i += 1;
313    }
314    n
315}
316
317const fn emit_sep(buf: &mut [u8], st: St) -> St {
318    if st.emitted {
319        St {
320            n: put_str(buf, st.n, ", "),
321            ..st
322        }
323    } else {
324        st
325    }
326}
327
328const fn emit_text(buf: &mut [u8], st: St, text: &str) -> St {
329    let st = emit_sep(buf, st);
330    St {
331        n: put_str(buf, st.n, text),
332        emitted: true,
333        ..st
334    }
335}
336
337const fn emit_named(
338    buf: &mut [u8],
339    st: St,
340    prefix: &str,
341    name: &str,
342    default: Option<DefaultRepr>,
343) -> St {
344    let st = emit_sep(buf, st);
345    let n = put_str(buf, st.n, prefix);
346    let n = put_str(buf, n, name);
347    let n = if let Some(default) = default {
348        let n = put_byte(buf, n, b'=');
349        put_default(buf, n, default)
350    } else {
351        n
352    };
353    St {
354        n,
355        emitted: true,
356        ..st
357    }
358}
359
360const HEX: &[u8; 16] = b"0123456789abcdef";
361
362const fn put_hex_byte(buf: &mut [u8], i: usize, b: u8) -> usize {
363    let n = put_str(buf, i, "\\x");
364    let n = put_byte(buf, n, HEX[(b >> 4) as usize]);
365    put_byte(buf, n, HEX[(b & 0xf) as usize])
366}
367
368const fn put_quoted(buf: &mut [u8], mut i: usize, bytes: &[u8], utf8: bool) -> usize {
369    i = put_byte(buf, i, b'\'');
370    let mut k = 0;
371    while k < bytes.len() {
372        let b = bytes[k];
373        if b == b'\\' {
374            i = put_str(buf, i, "\\\\");
375        } else if b == b'\'' {
376            i = put_str(buf, i, "\\'");
377        } else if b == b'\n' {
378            i = put_str(buf, i, "\\n");
379        } else if b == b'\r' {
380            i = put_str(buf, i, "\\r");
381        } else if b == b'\t' {
382            i = put_str(buf, i, "\\t");
383        } else if b < 0x20 || b == 0x7f || (!utf8 && b >= 0x80) {
384            i = put_hex_byte(buf, i, b);
385        } else {
386            i = put_byte(buf, i, b);
387        }
388        k += 1;
389    }
390    put_byte(buf, i, b'\'')
391}
392
393const fn put_u128(buf: &mut [u8], i: usize, mut v: u128) -> usize {
394    if v == 0 {
395        return put_byte(buf, i, b'0');
396    }
397    let mut digits = [0u8; 40];
398    let mut n = 0;
399    while v > 0 {
400        digits[n] = b'0' + (v % 10) as u8;
401        v /= 10;
402        n += 1;
403    }
404    let mut i = i;
405    while n > 0 {
406        n -= 1;
407        i = put_byte(buf, i, digits[n]);
408    }
409    i
410}
411
412const fn put_i128(buf: &mut [u8], i: usize, v: i128) -> usize {
413    if v < 0 {
414        let i = put_byte(buf, i, b'-');
415        put_u128(buf, i, (v as u128).wrapping_neg())
416    } else {
417        put_u128(buf, i, v as u128)
418    }
419}
420
421const fn put_exp(buf: &mut [u8], i: usize, exp: i32) -> usize {
422    let i = put_byte(buf, i, b'e');
423    let (i, exp) = if exp < 0 {
424        (put_byte(buf, i, b'-'), exp.wrapping_neg())
425    } else {
426        (put_byte(buf, i, b'+'), exp)
427    };
428    let exp = exp as u32;
429    if exp >= 10 {
430        let i = put_byte(buf, i, b'0' + (exp / 10) as u8);
431        put_byte(buf, i, b'0' + (exp % 10) as u8)
432    } else {
433        let i = put_byte(buf, i, b'0');
434        put_byte(buf, i, b'0' + exp as u8)
435    }
436}
437
438/// Shortest round-trip decimal, matching `float.__repr__` for finite values.
439const fn put_f64(buf: &mut [u8], i: usize, v: f64) -> usize {
440    if v.is_nan() {
441        return put_str(buf, i, "nan");
442    }
443    if v.is_infinite() {
444        return put_str(buf, i, if v.is_sign_negative() { "-inf" } else { "inf" });
445    }
446    if v == 0.0 {
447        return put_str(buf, i, if v.is_sign_negative() { "-0.0" } else { "0.0" });
448    }
449    let neg = v.is_sign_negative();
450    let mut i = if neg { put_byte(buf, i, b'-') } else { i };
451    let mut x = if neg { -v } else { v };
452    let mut exp: i32 = 0;
453    while x >= 10.0 && exp < 350 {
454        x /= 10.0;
455        exp += 1;
456    }
457    while x < 1.0 && exp > -350 {
458        x *= 10.0;
459        exp -= 1;
460    }
461    // 17 significant digits, then trim trailing zeros.
462    let mut digits = [0u8; 17];
463    let mut n = 0;
464    while n < 17 {
465        let d = x as u8;
466        digits[n] = d;
467        x = (x - d as f64) * 10.0;
468        n += 1;
469    }
470    if x >= 5.0 {
471        let mut k = 16;
472        loop {
473            if digits[k] < 9 {
474                digits[k] += 1;
475                break;
476            }
477            digits[k] = 0;
478            if k == 0 {
479                digits[0] = 1;
480                exp += 1;
481                break;
482            }
483            k -= 1;
484        }
485    }
486    while n > 1 && digits[n - 1] == 0 {
487        n -= 1;
488    }
489    let scientific = exp < -4 || exp >= 16;
490    if scientific {
491        i = put_byte(buf, i, b'0' + digits[0]);
492        if n > 1 {
493            i = put_byte(buf, i, b'.');
494            let mut k = 1;
495            while k < n {
496                i = put_byte(buf, i, b'0' + digits[k]);
497                k += 1;
498            }
499        }
500        put_exp(buf, i, exp)
501    } else if exp >= 0 {
502        let exp_us = exp as usize;
503        let mut k = 0;
504        while k <= exp_us && k < n {
505            i = put_byte(buf, i, b'0' + digits[k]);
506            k += 1;
507        }
508        while k <= exp_us {
509            i = put_byte(buf, i, b'0');
510            k += 1;
511        }
512        i = put_byte(buf, i, b'.');
513        if n as i32 > exp + 1 {
514            let mut k = exp as usize + 1;
515            while k < n {
516                i = put_byte(buf, i, b'0' + digits[k]);
517                k += 1;
518            }
519            i
520        } else {
521            put_byte(buf, i, b'0')
522        }
523    } else {
524        i = put_str(buf, i, "0.");
525        let mut z = 0;
526        while z < -exp - 1 {
527            i = put_byte(buf, i, b'0');
528            z += 1;
529        }
530        let mut k = 0;
531        while k < n {
532            i = put_byte(buf, i, b'0' + digits[k]);
533            k += 1;
534        }
535        i
536    }
537}
538
539const fn put_default(buf: &mut [u8], i: usize, default: DefaultRepr) -> usize {
540    match default {
541        DefaultRepr::None => put_str(buf, i, "None"),
542        DefaultRepr::Bool(true) => put_str(buf, i, "True"),
543        DefaultRepr::Bool(false) => put_str(buf, i, "False"),
544        DefaultRepr::Int(v) => put_i128(buf, i, v),
545        DefaultRepr::Float(v) => put_f64(buf, i, v),
546        DefaultRepr::Str(s) => put_quoted(buf, i, s.as_bytes(), true),
547        DefaultRepr::Bytes(b) => {
548            let i = put_byte(buf, i, b'b');
549            put_quoted(buf, i, b, false)
550        }
551        DefaultRepr::Raw(s) => put_str(buf, i, s),
552        DefaultRepr::Unrepresentable => put_str(buf, i, "<unrepresentable>"),
553    }
554}
555
556const fn param_name<'a>(name: &'a str, fallback: &'a str) -> &'a str {
557    if name.is_empty() { fallback } else { name }
558}
559
560const fn write_params(buf: &mut [u8], mut st: St, params: &[Param], fallback: &str) -> St {
561    let mut i = 0;
562    while i < params.len() {
563        st = write_one(buf, st, params[i], fallback);
564        i += 1;
565    }
566    st
567}
568
569const fn write_one(buf: &mut [u8], mut st: St, param: Param, fallback: &str) -> St {
570    let name = param_name(param.name, fallback);
571    match param.kind {
572        ParamKind::Flatten(None) => st,
573        ParamKind::Flatten(Some(inner)) => write_params(buf, st, inner, ""),
574        ParamKind::PositionalOnly => {
575            st = emit_named(buf, st, "", name, param.default);
576            st.po_left -= 1;
577            if st.po_left == 0 {
578                st = emit_text(buf, st, "/");
579            }
580            st
581        }
582        ParamKind::PositionalOrKeyword => emit_named(buf, st, "", name, param.default),
583        ParamKind::KeywordOnly => {
584            if !st.var_pos_seen && !st.star_emitted {
585                st = emit_text(buf, st, "*");
586                st.star_emitted = true;
587            }
588            emit_named(buf, st, "", name, param.default)
589        }
590        ParamKind::VarPositional => {
591            st.var_pos_seen = true;
592            emit_named(buf, st, "*", name, param.default)
593        }
594        ParamKind::VarKeyword => emit_named(buf, st, "**", name, param.default),
595    }
596}
597
598const fn write_args(buf: &mut [u8], mut st: St, args: &[SigArg]) -> St {
599    let mut i = 0;
600    while i < args.len() {
601        match args[i].params {
602            None => {
603                st = write_one(
604                    buf,
605                    st,
606                    Param {
607                        name: args[i].name,
608                        kind: ParamKind::PositionalOnly,
609                        default: None,
610                    },
611                    "",
612                );
613            }
614            Some(ps) => st = write_params(buf, st, ps, args[i].name),
615        }
616        i += 1;
617    }
618    st
619}
620
621#[cfg(test)]
622mod tests {
623    use super::{DefaultRepr, Param, ParamKind, St, write_one};
624
625    fn rendered(default: DefaultRepr) -> String {
626        let mut buf = [0u8; 64];
627        let st = write_one(
628            &mut buf,
629            St {
630                n: 0,
631                emitted: false,
632                po_left: 0,
633                var_pos_seen: false,
634                star_emitted: true,
635            },
636            Param {
637                name: "x",
638                kind: ParamKind::KeywordOnly,
639                default: Some(default),
640            },
641            "",
642        );
643        let text = core::str::from_utf8(&buf[..st.n]).unwrap();
644        text.split_once('=').unwrap().1.to_owned()
645    }
646
647    #[test]
648    fn default_repr_text() {
649        assert_eq!(rendered(DefaultRepr::None), "None");
650        assert_eq!(rendered(DefaultRepr::Bool(true)), "True");
651        assert_eq!(rendered(DefaultRepr::Bool(false)), "False");
652        assert_eq!(rendered(DefaultRepr::Int(-15)), "-15");
653        assert_eq!(rendered(DefaultRepr::Int(0)), "0");
654        assert_eq!(rendered(DefaultRepr::Float(0.0)), "0.0");
655        assert_eq!(rendered(DefaultRepr::Float(-1.0)), "-1.0");
656        assert_eq!(rendered(DefaultRepr::Float(5.0)), "5.0");
657        assert_eq!(rendered(DefaultRepr::Float(1e-9)), "1e-09");
658        assert_eq!(rendered(DefaultRepr::Float(f64::INFINITY)), "inf");
659        assert_eq!(rendered(DefaultRepr::Float(f64::NEG_INFINITY)), "-inf");
660        assert_eq!(rendered(DefaultRepr::Str("a'b\n")), r"'a\'b\n'");
661        assert_eq!(rendered(DefaultRepr::Bytes(b"a'b")), r"b'a\'b'");
662        assert_eq!(rendered(DefaultRepr::Raw("sys.maxsize")), "sys.maxsize");
663        assert_eq!(rendered(DefaultRepr::Unrepresentable), "<unrepresentable>");
664    }
665}