Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 42 additions & 8 deletions crates/vm/src/builtins/type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2472,7 +2472,12 @@ impl Constructor for PyType {

const SIGNATURE_END_MARKER: &str = ")\n--\n\n";
fn get_signature(doc: &str) -> Option<&str> {
doc.find(SIGNATURE_END_MARKER).map(|index| &doc[..=index])
let index = doc.find(SIGNATURE_END_MARKER)?;
// Like CPython's skip_signature, a blank paragraph ends the header search.
if doc[..index].contains("\n\n") {
return None;
}
Some(&doc[..=index])
}

fn find_signature<'a>(name: &str, doc: &'a str) -> Option<&'a str> {
Expand All @@ -2491,13 +2496,12 @@ pub(crate) fn get_text_signature_from_internal_doc<'a>(
// _PyType_DocWithoutSignature in CPython
fn doc_without_signature<'a>(name: &str, internal_doc: &'a str) -> &'a str {
// If the doc starts with the type name and a '(', it's a signature
if let Some(doc_without_sig) = find_signature(name, internal_doc) {
// Find where the signature ends
if let Some(sig_end_pos) = doc_without_sig.find(SIGNATURE_END_MARKER) {
let after_sig = &doc_without_sig[sig_end_pos + SIGNATURE_END_MARKER.len()..];
// Return the documentation after the signature, or empty string if none
return after_sig;
}
if let Some(doc_without_sig) = find_signature(name, internal_doc)
&& let Some(signature) = get_signature(doc_without_sig)
{
let after_sig = &doc_without_sig[signature.len() + SIGNATURE_END_MARKER.len() - 1..];
// Return the documentation after the signature, or empty string if none
return after_sig;
}
// If no signature found, return the whole doc
internal_doc
Expand All @@ -2509,6 +2513,36 @@ pub(crate) fn get_doc_from_internal_doc<'a>(name: &str, internal_doc: &'a str) -
(!doc.is_empty()).then_some(doc)
}

#[cfg(test)]
mod internal_doc_signature_tests {
use super::{get_doc_from_internal_doc, get_text_signature_from_internal_doc};

#[test]
fn signatures_stop_before_a_blank_paragraph() {
for doc in [
"f(x\n\ncontinued)\n--\n\nDocumentation.",
"f(x)\n\nExample: f(y)\n--\n\nDocumentation.",
] {
assert_eq!(get_text_signature_from_internal_doc("f", doc), None);
assert_eq!(get_doc_from_internal_doc("f", doc), Some(doc));
}
}

#[test]
fn multiline_signature_and_documentation_keep_their_boundary() {
let doc = "f($module, x,\n y=None, /)\n--\n\nFirst.\n\nSecond.";
assert_eq!(
get_text_signature_from_internal_doc("mod.f", doc),
Some("($module, x,\n y=None, /)")
);
assert_eq!(
get_doc_from_internal_doc("mod.f", doc),
Some("First.\n\nSecond.")
);
assert_eq!(get_doc_from_internal_doc("f", "f()\n--\n\n"), None);
}
}

pub(crate) fn rendered_item_doc(name: &str, doc: ItemDoc) -> Option<&'static str> {
if doc.len != 0 {
return db_doc(doc.offset, doc.len);
Expand Down
33 changes: 24 additions & 9 deletions crates/vm/src/function/signature.rs
Original file line number Diff line number Diff line change
Expand Up @@ -503,16 +503,12 @@ const fn put_exp(buf: &mut [u8], i: usize, exp: i32) -> usize {
(put_byte(buf, i, b'+'), exp)
};
let exp = exp as u32;
if exp >= 10 {
let i = put_byte(buf, i, b'0' + (exp / 10) as u8);
put_byte(buf, i, b'0' + (exp % 10) as u8)
} else {
let i = put_byte(buf, i, b'0');
put_byte(buf, i, b'0' + exp as u8)
}
let i = if exp < 10 { put_byte(buf, i, b'0') } else { i };
put_u128(buf, i, exp as u128)
}

/// Shortest round-trip decimal, matching `float.__repr__` for finite values.
/// Decimal rendering using up to 17 significant digits.
// This normalization-based formatter is not a shortest-round-trip algorithm.
const fn put_f64(buf: &mut [u8], i: usize, v: f64) -> usize {
if v.is_nan() {
return put_str(buf, i, "nan");
Expand Down Expand Up @@ -706,7 +702,7 @@ const fn write_args(buf: &mut [u8], mut st: St, args: &[SigArg]) -> St {

#[cfg(test)]
mod tests {
use super::{DefaultRepr, Param, ParamKind, SigArg, St, native_call_flags, write_one};
use super::{DefaultRepr, Param, ParamKind, SigArg, St, native_call_flags, put_exp, write_one};
use crate::function::PyMethodFlags;

fn rendered(default: DefaultRepr) -> String {
Expand All @@ -732,6 +728,25 @@ mod tests {
text.split_once('=').unwrap().1.to_owned()
}

#[test]
fn exponent_text_keeps_all_digits() {
for (exp, expected) in [
(0, "e+00"),
(9, "e+09"),
(-9, "e-09"),
(10, "e+10"),
(-99, "e-99"),
(100, "e+100"),
(308, "e+308"),
(-324, "e-324"),
] {
let mut buf = [0u8; 16];
let len = put_exp(&mut buf, 0, exp);
assert_eq!(core::str::from_utf8(&buf[..len]).unwrap(), expected);
assert_eq!(put_exp(&mut [], 0, exp), len);
}
}

#[test]
fn default_repr_text() {
assert_eq!(rendered(DefaultRepr::None), "None");
Expand Down
Loading