mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 10:44:36 +08:00
Fix missing workspace lints & fix lints (#8781)
Some of the workspace packages were missing the `[lints]` table. I enabled the lints then fixed the resulting errors.
This commit is contained in:
1 parent
30ff0329a2
commit
86c7fa20c4
37 files changed
+371
-259
No files matched your search
@@ -430,3 +430,6 @@ unnecessary_box_returns = "warn"
|
||||
unnecessary_join = "warn"
|
||||
unnecessary_wraps = "warn"
|
||||
unnested_or_patterns = "warn"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
+21
-8
@@ -1,10 +1,15 @@
|
||||
#![allow(clippy::iter_over_hash_type)]
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
use core::hint::black_box;
|
||||
use criterion::{
|
||||
Bencher, BenchmarkGroup, BenchmarkId, Criterion, Throughput, criterion_group, criterion_main,
|
||||
measurement::WallTime,
|
||||
};
|
||||
use rustpython_compiler::Mode;
|
||||
use rustpython_vm::{Interpreter, PyResult, Settings};
|
||||
use std::{collections::HashMap, hint::black_box, path::Path};
|
||||
use std::{collections::HashMap, path::Path};
|
||||
|
||||
/// `true` when the benchmarks are executed by the CodSpeed runner.
|
||||
///
|
||||
@@ -15,8 +20,8 @@ fn is_codspeed() -> bool {
|
||||
std::env::var_os("CODSPEED_ENV").is_some()
|
||||
}
|
||||
|
||||
fn bench_cpython_code(b: &mut Bencher, source: &str) {
|
||||
let c_str_source_head = std::ffi::CString::new(source).unwrap();
|
||||
fn bench_cpython_code(b: &mut Bencher<'_>, source: &str) {
|
||||
let c_str_source_head = alloc::ffi::CString::new(source).unwrap();
|
||||
let c_str_source = c_str_source_head.as_c_str();
|
||||
pyo3::Python::attach(|py| {
|
||||
b.iter(|| {
|
||||
@@ -27,7 +32,7 @@ fn bench_cpython_code(b: &mut Bencher, source: &str) {
|
||||
})
|
||||
}
|
||||
|
||||
fn bench_rustpython_code(b: &mut Bencher, name: &str, source: &str) {
|
||||
fn bench_rustpython_code(b: &mut Bencher<'_>, name: &str, source: &str) {
|
||||
// NOTE: Take long time.
|
||||
let mut settings = Settings::default();
|
||||
settings.path_list.push("Lib/".to_string());
|
||||
@@ -41,13 +46,17 @@ fn bench_rustpython_code(b: &mut Bencher, name: &str, source: &str) {
|
||||
b.iter(|| {
|
||||
let code = vm.compile(source, Mode::Exec, name).unwrap();
|
||||
let scope = vm.new_scope_with_builtins();
|
||||
let res: PyResult = vm.run_code_obj(code.clone(), scope);
|
||||
let res: PyResult = vm.run_code_obj(code, scope);
|
||||
vm.unwrap_pyresult(res);
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn benchmark_file_execution(group: &mut BenchmarkGroup<WallTime>, name: &str, contents: &str) {
|
||||
pub fn benchmark_file_execution(
|
||||
group: &mut BenchmarkGroup<'_, WallTime>,
|
||||
name: &str,
|
||||
contents: &str,
|
||||
) {
|
||||
if !is_codspeed() {
|
||||
group.bench_function(BenchmarkId::new(name, "cpython"), |b| {
|
||||
bench_cpython_code(b, contents)
|
||||
@@ -58,7 +67,11 @@ pub fn benchmark_file_execution(group: &mut BenchmarkGroup<WallTime>, name: &str
|
||||
});
|
||||
}
|
||||
|
||||
pub fn benchmark_file_parsing(group: &mut BenchmarkGroup<WallTime>, name: &str, contents: &str) {
|
||||
pub fn benchmark_file_parsing(
|
||||
group: &mut BenchmarkGroup<'_, WallTime>,
|
||||
name: &str,
|
||||
contents: &str,
|
||||
) {
|
||||
group.throughput(Throughput::Bytes(contents.len() as u64));
|
||||
group.bench_function(BenchmarkId::new("rustpython", name), |b| {
|
||||
b.iter(|| ruff_python_parser::parse_module(contents).unwrap())
|
||||
@@ -81,7 +94,7 @@ pub fn benchmark_file_parsing(group: &mut BenchmarkGroup<WallTime>, name: &str,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn benchmark_pystone(group: &mut BenchmarkGroup<WallTime>, contents: String) {
|
||||
pub fn benchmark_pystone(group: &mut BenchmarkGroup<'_, WallTime>, contents: String) {
|
||||
// Default is 50_000. This takes a while, so reduce it to 30k.
|
||||
for idx in (10_000..=30_000).step_by(10_000) {
|
||||
let code_with_loops = format!("LOOPS = {idx}\n{contents}");
|
||||
|
||||
@@ -5,10 +5,7 @@ use criterion::{
|
||||
use pyo3::types::PyAnyMethods;
|
||||
use rustpython_compiler::Mode;
|
||||
use rustpython_vm::{AsObject, Interpreter, PyResult, Settings};
|
||||
use std::{
|
||||
fs, io,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
use std::{fs, io, path::Path};
|
||||
|
||||
// List of microbenchmarks to skip.
|
||||
//
|
||||
@@ -36,7 +33,7 @@ pub struct MicroBenchmark {
|
||||
iterate: bool,
|
||||
}
|
||||
|
||||
fn bench_cpython_code(group: &mut BenchmarkGroup<WallTime>, bench: &MicroBenchmark) {
|
||||
fn bench_cpython_code(group: &mut BenchmarkGroup<'_, WallTime>, bench: &MicroBenchmark) {
|
||||
pyo3::Python::attach(|py| {
|
||||
let setup_name = format!("{}_setup", bench.name);
|
||||
let setup_code = cpy_compile_code(py, &bench.setup, &setup_name).unwrap();
|
||||
@@ -49,8 +46,8 @@ fn bench_cpython_code(group: &mut BenchmarkGroup<WallTime>, bench: &MicroBenchma
|
||||
let exec = builtins.getattr("exec").expect("no exec in builtins");
|
||||
|
||||
let bench_func = |(globals, locals): &mut (
|
||||
pyo3::Bound<pyo3::types::PyDict>,
|
||||
pyo3::Bound<pyo3::types::PyDict>,
|
||||
pyo3::Bound<'_, pyo3::types::PyDict>,
|
||||
pyo3::Bound<'_, pyo3::types::PyDict>,
|
||||
)| {
|
||||
let res = exec.call((&code, &*globals, &*locals), None);
|
||||
if let Err(e) = res {
|
||||
@@ -107,7 +104,7 @@ fn cpy_compile_code<'a>(
|
||||
.expect("compile() should return a code object"))
|
||||
}
|
||||
|
||||
fn bench_rustpython_code(group: &mut BenchmarkGroup<WallTime>, bench: &MicroBenchmark) {
|
||||
fn bench_rustpython_code(group: &mut BenchmarkGroup<'_, WallTime>, bench: &MicroBenchmark) {
|
||||
let mut settings = Settings::default();
|
||||
settings.path_list.push("Lib/".to_string());
|
||||
settings.write_bytecode = false;
|
||||
@@ -208,11 +205,11 @@ pub fn criterion_benchmark(c: &mut Criterion) {
|
||||
.unwrap()
|
||||
.collect::<io::Result<_>>()
|
||||
.unwrap();
|
||||
let paths: Vec<PathBuf> = dirs.iter().map(|p| p.path()).collect();
|
||||
|
||||
let benchmarks: Vec<MicroBenchmark> = paths
|
||||
.into_iter()
|
||||
.map(|p| {
|
||||
let benchmarks: Vec<MicroBenchmark> = dirs
|
||||
.iter()
|
||||
.map(|d| {
|
||||
let p = d.path();
|
||||
let name = p.file_name().unwrap().to_os_string();
|
||||
let contents = fs::read_to_string(p).unwrap();
|
||||
let iterate = contents.contains("ITERATIONS");
|
||||
|
||||
@@ -1253,8 +1253,7 @@ fn assemble_location_info(
|
||||
let mut prev_line = first_line;
|
||||
let mut loc = no_linetable_location();
|
||||
let mut size = 0;
|
||||
for i in 0..instr_sequence.instr_used {
|
||||
let entry = &instr_sequence.instrs[i];
|
||||
for entry in instr_sequence.instrs.iter().take(instr_sequence.instr_used) {
|
||||
let instr_loc = entry.info.instruction_linetable_location();
|
||||
if !same_location(loc, instr_loc) {
|
||||
assemble_emit_location(&mut linetable, loc, size, &mut prev_line, debug_ranges)?;
|
||||
@@ -2772,8 +2771,7 @@ impl Blocks {
|
||||
let mut block_idx = BlockIdx(0);
|
||||
while block_idx != BlockIdx::NULL {
|
||||
let block = &self[block_idx];
|
||||
for i in 0..block.instruction_used {
|
||||
let instr = &block.instructions[i];
|
||||
for instr in block.instructions.iter().take(block.instruction_used) {
|
||||
if instr.instr.has_const() {
|
||||
let index = u32::from(instr.arg) as usize;
|
||||
debug_assert!(index < nconsts);
|
||||
@@ -6596,8 +6594,11 @@ fn cfg_from_instruction_sequence(
|
||||
if let Some(annotations_code) = &annotations_code {
|
||||
debug_assert!(annotations_code.label_map.is_none());
|
||||
debug_assert_eq!(annotations_code.label_map_allocation, 0);
|
||||
for j in 0..annotations_code.instr_used {
|
||||
let ann_entry = annotations_code.instrs[j];
|
||||
for ann_entry in annotations_code
|
||||
.instrs
|
||||
.iter()
|
||||
.take(annotations_code.instr_used)
|
||||
{
|
||||
debug_assert!(!ann_entry.info.instr.has_target());
|
||||
let mut info = ann_entry.info;
|
||||
info.target = BlockIdx::NULL;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use core::f64;
|
||||
use core::f64::consts::LOG10_2;
|
||||
use malachite_bigint::{BigInt, ToBigInt};
|
||||
use num_traits::{Signed, ToPrimitive};
|
||||
|
||||
@@ -240,9 +240,8 @@ pub fn round_float_digits(x: f64, ndigits: i32) -> Option<f64> {
|
||||
return Some(x);
|
||||
}
|
||||
|
||||
const NDIGITS_MAX: i32 =
|
||||
((f64::MANTISSA_DIGITS as i32 - f64::MIN_EXP) as f64 * f64::consts::LOG10_2) as i32;
|
||||
const NDIGITS_MIN: i32 = -(((f64::MAX_EXP + 1) as f64 * f64::consts::LOG10_2) as i32);
|
||||
const NDIGITS_MAX: i32 = ((f64::MANTISSA_DIGITS as i32 - f64::MIN_EXP) as f64 * LOG10_2) as i32;
|
||||
const NDIGITS_MIN: i32 = -(((f64::MAX_EXP + 1) as f64 * LOG10_2) as i32);
|
||||
|
||||
if ndigits > NDIGITS_MAX {
|
||||
return Some(x);
|
||||
|
||||
@@ -146,13 +146,13 @@ impl Wtf8Index {
|
||||
};
|
||||
let mut index = group_min << 6;
|
||||
let mut pos = base;
|
||||
for entry in 0..entries {
|
||||
let at = base + self.groups[group_min].ofs[entry] as usize;
|
||||
for (i, &entry) in self.groups[group_min].ofs.iter().enumerate().take(entries) {
|
||||
let at = base + entry as usize;
|
||||
if at >= bytepos {
|
||||
break;
|
||||
}
|
||||
pos = at;
|
||||
index = (group_min << 6) + (entry << 2) + 1;
|
||||
index = (group_min << 6) + (i << 2) + 1;
|
||||
}
|
||||
while pos < bytepos {
|
||||
pos = next_pos(data, pos);
|
||||
|
||||
@@ -50,7 +50,7 @@ nix = { workspace = true }
|
||||
rustix = { workspace = true }
|
||||
|
||||
[target.'cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))'.dependencies]
|
||||
num_cpus = "1.17.0"
|
||||
num_cpus = { workspace = true }
|
||||
|
||||
[target.'cfg(not(any(target_os = "ios", target_os = "android", target_os = "windows", target_arch = "wasm32", target_os = "redox")))'.dependencies]
|
||||
mac_address = { workspace = true }
|
||||
@@ -67,8 +67,8 @@ which = { workspace = true }
|
||||
termios = { workspace = true }
|
||||
|
||||
[target.'cfg(any(unix, windows))'.dependencies]
|
||||
memmap2 = "0.9.10"
|
||||
libloading = "0.9"
|
||||
memmap2 = { workspace = true }
|
||||
libloading = { workspace = true }
|
||||
|
||||
[target.'cfg(all(any(target_os = "linux", target_os = "macos", target_os = "windows", target_os = "android"), not(any(target_env = "musl", target_env = "sgx"))))'.dependencies]
|
||||
libffi = { workspace = true, features = ["system"] }
|
||||
|
||||
@@ -179,10 +179,11 @@ impl SemHandle {
|
||||
|
||||
/// A handle rebuilt from the integer a `SemLock` stored.
|
||||
///
|
||||
/// # Safety
|
||||
/// `Drop` closes the semaphore. A caller that still owns that close
|
||||
/// must forget this value.
|
||||
#[inline]
|
||||
pub unsafe fn from_raw(raw: *mut sem_t) -> Self {
|
||||
pub const unsafe fn from_raw(raw: *mut sem_t) -> Self {
|
||||
Self { raw }
|
||||
}
|
||||
|
||||
|
||||
@@ -235,7 +235,6 @@ pub fn rename(
|
||||
///
|
||||
/// On Windows, this supports the full u32 range including STATUS_CONTROL_C_EXIT (0xC000013A).
|
||||
/// On other platforms, only the lower 8 bits are used.
|
||||
#[must_use]
|
||||
pub fn exit_code(code: u32) -> ExitCode {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
|
||||
@@ -8,6 +8,9 @@ repository = { workspace = true }
|
||||
license = { workspace = true }
|
||||
rust-version = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
rustpython-unicode = { workspace = true }
|
||||
rustpython-wtf8 = { workspace = true }
|
||||
|
||||
@@ -33,6 +33,7 @@ fn component_to_string(value: f64) -> String {
|
||||
}
|
||||
|
||||
/// Convert a complex number to a string.
|
||||
#[must_use]
|
||||
pub fn to_string(re: f64, im: f64) -> String {
|
||||
let mut im_part = component_to_string(im);
|
||||
im_part.push('j');
|
||||
@@ -41,9 +42,8 @@ pub fn to_string(re: f64, im: f64) -> String {
|
||||
let re_part = if re == 0.0 {
|
||||
if re.is_sign_positive() {
|
||||
return im_part;
|
||||
} else {
|
||||
"-0".to_owned()
|
||||
}
|
||||
"-0".to_owned()
|
||||
} else {
|
||||
component_to_string(re)
|
||||
};
|
||||
|
||||
@@ -9,6 +9,7 @@ pub enum Quote {
|
||||
|
||||
impl Quote {
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub const fn swap(self) -> Self {
|
||||
match self {
|
||||
Self::Single => Self::Double,
|
||||
@@ -17,6 +18,7 @@ impl Quote {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub const fn to_byte(&self) -> u8 {
|
||||
match self {
|
||||
Self::Single => b'\'',
|
||||
@@ -25,6 +27,7 @@ impl Quote {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub const fn to_char(&self) -> char {
|
||||
match self {
|
||||
Self::Single => '\'',
|
||||
@@ -96,20 +99,24 @@ pub struct UnicodeEscape<'a> {
|
||||
|
||||
impl<'a> UnicodeEscape<'a> {
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub const fn with_forced_quote(source: &'a Wtf8, quote: Quote) -> Self {
|
||||
let layout = EscapeLayout { quote, len: None };
|
||||
Self { source, layout }
|
||||
}
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn with_preferred_quote(source: &'a Wtf8, quote: Quote) -> Self {
|
||||
let layout = Self::repr_layout(source, quote);
|
||||
Self { source, layout }
|
||||
}
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn new_repr(source: &'a Wtf8) -> Self {
|
||||
Self::with_preferred_quote(source, Quote::Single)
|
||||
}
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub const fn str_repr<'r>(&'a self) -> StrRepr<'r, 'a> {
|
||||
StrRepr(self)
|
||||
}
|
||||
@@ -125,6 +132,7 @@ impl StrRepr<'_, '_> {
|
||||
formatter.write_char(quote)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn to_string(&self) -> Option<String> {
|
||||
let mut s = String::with_capacity(self.0.layout().len?);
|
||||
self.write(&mut s).unwrap();
|
||||
@@ -141,6 +149,7 @@ impl core::fmt::Display for StrRepr<'_, '_> {
|
||||
impl UnicodeEscape<'_> {
|
||||
const REPR_RESERVED_LEN: usize = 2; // for quotes
|
||||
|
||||
#[must_use]
|
||||
pub fn repr_layout(source: &Wtf8, preferred_quote: Quote) -> EscapeLayout {
|
||||
Self::output_layout_with_checker(source, preferred_quote, |a, b| {
|
||||
Some((a as isize).checked_add(b as isize)? as usize)
|
||||
@@ -284,36 +293,43 @@ pub struct AsciiEscape<'a> {
|
||||
|
||||
impl<'a> AsciiEscape<'a> {
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub const fn new(source: &'a [u8], layout: EscapeLayout) -> Self {
|
||||
Self { source, layout }
|
||||
}
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub const fn with_forced_quote(source: &'a [u8], quote: Quote) -> Self {
|
||||
let layout = EscapeLayout { quote, len: None };
|
||||
Self { source, layout }
|
||||
}
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn with_preferred_quote(source: &'a [u8], quote: Quote) -> Self {
|
||||
let layout = Self::repr_layout(source, quote);
|
||||
Self { source, layout }
|
||||
}
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn new_repr(source: &'a [u8]) -> Self {
|
||||
Self::with_preferred_quote(source, Quote::Single)
|
||||
}
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub const fn bytes_repr<'r>(&'a self) -> BytesRepr<'r, 'a> {
|
||||
BytesRepr(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsciiEscape<'_> {
|
||||
#[must_use]
|
||||
pub fn repr_layout(source: &[u8], preferred_quote: Quote) -> EscapeLayout {
|
||||
Self::output_layout_with_checker(source, preferred_quote, 3, |a, b| {
|
||||
Some((a as isize).checked_add(b as isize)? as usize)
|
||||
})
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn named_repr_layout(source: &[u8], name: &str) -> EscapeLayout {
|
||||
Self::output_layout_with_checker(source, Quote::Single, name.len() + 2 + 3, |a, b| {
|
||||
Some((a as isize).checked_add(b as isize)? as usize)
|
||||
@@ -436,6 +452,7 @@ impl BytesRepr<'_, '_> {
|
||||
formatter.write_char(quote)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn to_string(&self) -> Option<String> {
|
||||
let mut s = String::with_capacity(self.0.layout().len?);
|
||||
self.write(&mut s).unwrap();
|
||||
|
||||
+95
-86
@@ -2,13 +2,14 @@ use crate::format::Case;
|
||||
use alloc::borrow::ToOwned;
|
||||
use alloc::format;
|
||||
use alloc::string::{String, ToString};
|
||||
use core::f64;
|
||||
use num_traits::Zero;
|
||||
|
||||
#[must_use]
|
||||
pub fn parse_str(literal: &str) -> Option<f64> {
|
||||
parse_inner(literal.trim().as_bytes())
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn parse_bytes(literal: &[u8]) -> Option<f64> {
|
||||
parse_inner(literal.trim_ascii())
|
||||
}
|
||||
@@ -25,6 +26,7 @@ fn parse_inner(literal: &[u8]) -> Option<f64> {
|
||||
f64::from_lexical_with_options::<PYTHON_STRING>(literal, &Options::new()).ok()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn is_integer(v: f64) -> bool {
|
||||
v.is_finite() && v.fract() == 0.0
|
||||
}
|
||||
@@ -47,6 +49,7 @@ fn format_inf(case: Case) -> String {
|
||||
inf.to_string()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn decimal_point_or_empty(precision: usize, alternate_form: bool) -> &'static str {
|
||||
match (precision, alternate_form) {
|
||||
(0, true) => ".",
|
||||
@@ -70,15 +73,18 @@ pub const FMT_MAX_PRECISION: usize = u16::MAX as usize;
|
||||
pub const FMT_MAX_EXP_PRECISION: usize = u16::MAX as usize - 1;
|
||||
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn clamp_fmt_precision(precision: usize) -> usize {
|
||||
core::cmp::min(precision, FMT_MAX_PRECISION)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn clamp_exp_precision(precision: usize) -> usize {
|
||||
core::cmp::min(precision, FMT_MAX_EXP_PRECISION)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn format_fixed(precision: usize, magnitude: f64, case: Case, alternate_form: bool) -> String {
|
||||
match magnitude {
|
||||
magnitude if magnitude.is_finite() => {
|
||||
@@ -103,6 +109,7 @@ pub fn format_fixed(precision: usize, magnitude: f64, case: Case, alternate_form
|
||||
|
||||
// Formats floats into Python style exponent notation, by first formatting in Rust style
|
||||
// exponent notation (`1.0000e0`), then convert to Python style (`1.0000e+00`).
|
||||
#[must_use]
|
||||
pub fn format_exponent(
|
||||
precision: usize,
|
||||
magnitude: f64,
|
||||
@@ -166,6 +173,7 @@ fn remove_trailing_decimal_point(s: String) -> String {
|
||||
s
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn format_general(
|
||||
precision: usize,
|
||||
magnitude: f64,
|
||||
@@ -176,7 +184,7 @@ pub fn format_general(
|
||||
match magnitude {
|
||||
magnitude if magnitude.is_finite() => {
|
||||
let exp_precision = clamp_exp_precision(precision.saturating_sub(1));
|
||||
let r_exp = format!("{:.*e}", exp_precision, magnitude);
|
||||
let r_exp = format!("{magnitude:.exp_precision$e}");
|
||||
let mut parts = r_exp.splitn(2, 'e');
|
||||
let base = parts.next().unwrap();
|
||||
let exponent = parts.next().unwrap().parse::<i64>().unwrap();
|
||||
@@ -230,7 +238,7 @@ pub(crate) fn prefer_cpython_tie_repr(s: String, value: f64) -> String {
|
||||
|
||||
let mut candidate = s.clone();
|
||||
candidate.replace_range(
|
||||
digit_pos..digit_pos + 1,
|
||||
digit_pos..=digit_pos,
|
||||
core::str::from_utf8(&[decremented]).unwrap(),
|
||||
);
|
||||
if parse_str(&candidate).is_none_or(|parsed| parsed.to_bits() != value.to_bits()) {
|
||||
@@ -266,9 +274,7 @@ fn parse_decimal_rational(s: &str) -> Option<(u128, u32)> {
|
||||
};
|
||||
let significand = mantissa.strip_prefix('-').unwrap_or(mantissa);
|
||||
let dot_pos = significand.find('.');
|
||||
let frac_digits = dot_pos
|
||||
.map(|pos| significand.len().saturating_sub(pos + 1))
|
||||
.unwrap_or(0);
|
||||
let frac_digits = dot_pos.map_or(0, |pos| significand.len().saturating_sub(pos + 1));
|
||||
let mut digits = String::with_capacity(significand.len());
|
||||
for ch in significand.chars() {
|
||||
if ch != '.' {
|
||||
@@ -317,6 +323,7 @@ fn decimal_distance_to_f64(s: &str, value: f64) -> Option<u128> {
|
||||
}
|
||||
|
||||
// TODO: rewrite using format_general
|
||||
#[must_use]
|
||||
pub fn to_string(value: f64) -> String {
|
||||
let lit = format!("{value:e}");
|
||||
if let Some(position) = lit.find('e') {
|
||||
@@ -339,37 +346,7 @@ pub fn to_string(value: f64) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::to_string;
|
||||
|
||||
#[test]
|
||||
fn repr_uses_cpython_tie_digit_for_power_of_two() {
|
||||
assert_eq!(to_string(2.0f64.powi(-25)), "2.9802322387695312e-08");
|
||||
assert_eq!(to_string((-2.0f64).powi(-25)), "-2.9802322387695312e-08");
|
||||
assert_eq!(to_string(2.0f64.powi(-26)), "1.4901161193847656e-08");
|
||||
assert_eq!(
|
||||
to_string(2.0f64.powi(-14) - 2.0f64.powi(-25)),
|
||||
"6.1005353927612305e-05"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn repr_normal_range_uses_cpython_tie_digit() {
|
||||
// Rust's shortest formatter yields "161852602146008.13" for this
|
||||
// value; round-half-to-even (what `repr` uses) picks "…08.12".
|
||||
assert_eq!(
|
||||
to_string(f64::from_bits(0x42e26687db6b9b04)),
|
||||
"161852602146008.12"
|
||||
);
|
||||
// Non-tie values are left untouched.
|
||||
assert_eq!(to_string(1.5), "1.5");
|
||||
assert_eq!(to_string(0.1), "0.1");
|
||||
assert_eq!(to_string(12.34), "12.34");
|
||||
assert_eq!(to_string(100.0), "100.0");
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn from_hex(s: &str) -> Option<f64> {
|
||||
if let Ok(f) = hexf_parse::parse_hexf64(s, false) {
|
||||
return Some(f);
|
||||
@@ -418,6 +395,7 @@ pub fn from_hex(s: &str) -> Option<f64> {
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn to_hex(value: f64) -> String {
|
||||
let bits = value.to_bits();
|
||||
let sign_fmt = if bits >> 63 != 0 { "-" } else { "" };
|
||||
@@ -440,55 +418,86 @@ pub fn to_hex(value: f64) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_hex() {
|
||||
use rand::RngExt;
|
||||
assert_eq!(to_hex(f64::from_bits(1)), "0x0.0000000000001p-1022");
|
||||
assert_eq!(to_hex(f64::from_bits(2)), "0x0.0000000000002p-1022");
|
||||
assert_eq!(to_hex(-f64::from_bits(1)), "-0x0.0000000000001p-1022");
|
||||
assert_eq!(to_hex(f64::MIN_POSITIVE), "0x1.0000000000000p-1022");
|
||||
for _ in 0..20000 {
|
||||
let bytes = rand::rng().random::<u64>();
|
||||
let f = f64::from_bits(bytes);
|
||||
if !f.is_finite() {
|
||||
continue;
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn repr_uses_cpython_tie_digit_for_power_of_two() {
|
||||
assert_eq!(to_string(2.0f64.powi(-25)), "2.9802322387695312e-08");
|
||||
assert_eq!(to_string((-2.0f64).powi(-25)), "-2.9802322387695312e-08");
|
||||
assert_eq!(to_string(2.0f64.powi(-26)), "1.4901161193847656e-08");
|
||||
assert_eq!(
|
||||
to_string(2.0f64.powi(-14) - 2.0f64.powi(-25)),
|
||||
"6.1005353927612305e-05"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn repr_normal_range_uses_cpython_tie_digit() {
|
||||
// Rust's shortest formatter yields "161852602146008.13" for this
|
||||
// value; round-half-to-even (what `repr` uses) picks "…08.12".
|
||||
assert_eq!(
|
||||
to_string(f64::from_bits(0x42e26687db6b9b04)),
|
||||
"161852602146008.12"
|
||||
);
|
||||
// Non-tie values are left untouched.
|
||||
assert_eq!(to_string(1.5), "1.5");
|
||||
assert_eq!(to_string(0.1), "0.1");
|
||||
assert_eq!(to_string(12.34), "12.34");
|
||||
assert_eq!(to_string(100.0), "100.0");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_hex_works() {
|
||||
use rand::RngExt;
|
||||
assert_eq!(to_hex(f64::from_bits(1)), "0x0.0000000000001p-1022");
|
||||
assert_eq!(to_hex(f64::from_bits(2)), "0x0.0000000000002p-1022");
|
||||
assert_eq!(to_hex(-f64::from_bits(1)), "-0x0.0000000000001p-1022");
|
||||
assert_eq!(to_hex(f64::MIN_POSITIVE), "0x1.0000000000000p-1022");
|
||||
for _ in 0..20000 {
|
||||
let bytes = rand::rng().random::<u64>();
|
||||
let f = f64::from_bits(bytes);
|
||||
if !f.is_finite() {
|
||||
continue;
|
||||
}
|
||||
let hex = to_hex(f);
|
||||
// println!("{} -> {}", f, hex);
|
||||
let roundtrip = hexf_parse::parse_hexf64(&hex, false).unwrap();
|
||||
// println!(" -> {}", roundtrip);
|
||||
assert!(f == roundtrip, "{f} {hex} {roundtrip}");
|
||||
}
|
||||
let hex = to_hex(f);
|
||||
// println!("{} -> {}", f, hex);
|
||||
let roundtrip = hexf_parse::parse_hexf64(&hex, false).unwrap();
|
||||
// println!(" -> {}", roundtrip);
|
||||
assert!(f == roundtrip, "{f} {hex} {roundtrip}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remove_trailing_zeros_works() {
|
||||
assert!(remove_trailing_zeros(String::from("100")) == *"1");
|
||||
assert!(remove_trailing_zeros(String::from("100.00")) == *"100.");
|
||||
|
||||
// leave leading zeros untouched
|
||||
assert!(remove_trailing_zeros(String::from("001")) == *"001");
|
||||
|
||||
// leave strings untouched if they don't end with 0
|
||||
assert!(remove_trailing_zeros(String::from("101")) == *"101");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remove_trailing_decimal_point_works() {
|
||||
assert!(remove_trailing_decimal_point(String::from("100.")) == *"100");
|
||||
assert!(remove_trailing_decimal_point(String::from("1.")) == *"1");
|
||||
|
||||
// leave leading decimal points untouched
|
||||
assert!(remove_trailing_decimal_point(String::from(".5")) == *".5");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn maybe_remove_trailing_redundant_chars_works() {
|
||||
assert!(maybe_remove_trailing_redundant_chars(String::from("100."), true) == *"100.");
|
||||
assert!(maybe_remove_trailing_redundant_chars(String::from("100."), false) == *"100");
|
||||
assert!(maybe_remove_trailing_redundant_chars(String::from("1."), false) == *"1");
|
||||
assert!(maybe_remove_trailing_redundant_chars(String::from("10.0"), false) == *"10");
|
||||
|
||||
// don't truncate integers
|
||||
assert!(maybe_remove_trailing_redundant_chars(String::from("1000"), false) == *"1000");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_remove_trailing_zeros() {
|
||||
assert!(remove_trailing_zeros(String::from("100")) == *"1");
|
||||
assert!(remove_trailing_zeros(String::from("100.00")) == *"100.");
|
||||
|
||||
// leave leading zeros untouched
|
||||
assert!(remove_trailing_zeros(String::from("001")) == *"001");
|
||||
|
||||
// leave strings untouched if they don't end with 0
|
||||
assert!(remove_trailing_zeros(String::from("101")) == *"101");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_remove_trailing_decimal_point() {
|
||||
assert!(remove_trailing_decimal_point(String::from("100.")) == *"100");
|
||||
assert!(remove_trailing_decimal_point(String::from("1.")) == *"1");
|
||||
|
||||
// leave leading decimal points untouched
|
||||
assert!(remove_trailing_decimal_point(String::from(".5")) == *".5");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_maybe_remove_trailing_redundant_chars() {
|
||||
assert!(maybe_remove_trailing_redundant_chars(String::from("100."), true) == *"100.");
|
||||
assert!(maybe_remove_trailing_redundant_chars(String::from("100."), false) == *"100");
|
||||
assert!(maybe_remove_trailing_redundant_chars(String::from("1."), false) == *"1");
|
||||
assert!(maybe_remove_trailing_redundant_chars(String::from("10.0"), false) == *"10");
|
||||
|
||||
// don't truncate integers
|
||||
assert!(maybe_remove_trailing_redundant_chars(String::from("1000"), false) == *"1000");
|
||||
}
|
||||
@@ -2179,7 +2179,7 @@ mod _sqlite3 {
|
||||
.flat_map(|x| x.to_uppercase())
|
||||
.collect::<String>();
|
||||
if let Some(converter) = converters().get_item_opt(&col_name, vm)? {
|
||||
cast_map.push(Some(converter.clone()));
|
||||
cast_map.push(Some(converter));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -2189,7 +2189,7 @@ mod _sqlite3 {
|
||||
if let Some(decltype) = decltype.split_terminator(&[' ', '(']).next() {
|
||||
let decltype = decltype.to_uppercase();
|
||||
if let Some(converter) = converters().get_item_opt(&decltype, vm)? {
|
||||
cast_map.push(Some(converter.clone()));
|
||||
cast_map.push(Some(converter));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -262,7 +262,6 @@ pub(crate) mod _hashlib {
|
||||
}
|
||||
|
||||
#[repr(C, align(16))]
|
||||
#[repr(align(16))]
|
||||
struct RawHashState {
|
||||
words: [MaybeUninit<usize>; backend::HASH_STATE_STORAGE_WORDS],
|
||||
}
|
||||
@@ -369,7 +368,6 @@ pub(crate) mod _hashlib {
|
||||
}
|
||||
|
||||
#[repr(C, align(16))]
|
||||
#[repr(align(16))]
|
||||
struct RawHmacState {
|
||||
words: [MaybeUninit<usize>; backend::HMAC_STATE_STORAGE_WORDS],
|
||||
}
|
||||
|
||||
@@ -838,7 +838,7 @@ impl PyRef<PyBaseException> {
|
||||
if key_str.as_bytes().starts_with(b"__") {
|
||||
continue;
|
||||
}
|
||||
self.as_object().set_attr(&key_str, value.clone(), vm)?;
|
||||
self.as_object().set_attr(&key_str, value, vm)?;
|
||||
}
|
||||
}
|
||||
Ok(vm.ctx.none())
|
||||
|
||||
@@ -681,8 +681,8 @@ impl GcState {
|
||||
// Reset counts for generations whose objects were promoted away.
|
||||
// For gen2 (oldest), survivors stay in-place so don't reset gen2 count.
|
||||
let reset_end = if generation >= 2 { 2 } else { generation + 1 };
|
||||
for i in 0..reset_end {
|
||||
self.counts[i].store(0, Ordering::Relaxed);
|
||||
for count in self.counts.iter().take(reset_end) {
|
||||
count.store(0, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
let duration = elapsed_secs(start_time);
|
||||
@@ -857,8 +857,8 @@ impl GcState {
|
||||
drop(gen_locks);
|
||||
self.promote_survivors(generation, &survivor_refs);
|
||||
let reset_end = if generation >= 2 { 2 } else { generation + 1 };
|
||||
for i in 0..reset_end {
|
||||
self.counts[i].store(0, Ordering::Relaxed);
|
||||
for count in self.counts.iter().take(reset_end) {
|
||||
count.store(0, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
let duration = elapsed_secs(start_time);
|
||||
@@ -880,8 +880,8 @@ impl GcState {
|
||||
if unreachable_refs.is_empty() {
|
||||
self.promote_survivors(generation, &survivor_refs);
|
||||
let reset_end = if generation >= 2 { 2 } else { generation + 1 };
|
||||
for i in 0..reset_end {
|
||||
self.counts[i].store(0, Ordering::Relaxed);
|
||||
for count in self.counts.iter().take(reset_end) {
|
||||
count.store(0, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
let duration = elapsed_secs(start_time);
|
||||
@@ -1107,8 +1107,8 @@ impl GcState {
|
||||
// Reset counts for generations whose objects were promoted away.
|
||||
// For gen2 (oldest), survivors stay in-place so don't reset gen2 count.
|
||||
let reset_end = if generation >= 2 { 2 } else { generation + 1 };
|
||||
for i in 0..reset_end {
|
||||
self.counts[i].store(0, Ordering::Relaxed);
|
||||
for count in self.counts.iter().take(reset_end) {
|
||||
count.store(0, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
let duration = elapsed_secs(start_time);
|
||||
|
||||
@@ -417,7 +417,6 @@ impl PyCStructType {
|
||||
|
||||
// Create CField descriptor with padding-adjusted offset
|
||||
let field_type_ref = field_type
|
||||
.clone()
|
||||
.downcast::<PyType>()
|
||||
.map_err(|_| vm.new_type_error("_fields_ type must be a ctypes type"))?;
|
||||
|
||||
|
||||
@@ -274,7 +274,6 @@ impl PyCUnionType {
|
||||
|
||||
// For Union, all fields start at offset 0
|
||||
let field_type_ref = field_type
|
||||
.clone()
|
||||
.downcast::<PyType>()
|
||||
.map_err(|_| vm.new_type_error("_fields_ type must be a ctypes type"))?;
|
||||
|
||||
|
||||
@@ -537,7 +537,7 @@ mod _io {
|
||||
for line in it.iter(vm)? {
|
||||
let line = line?;
|
||||
let line_len = line.length(vm)?;
|
||||
ret.push(line.clone());
|
||||
ret.push(line);
|
||||
full_len += line_len;
|
||||
if full_len > hint {
|
||||
break;
|
||||
|
||||
@@ -238,7 +238,7 @@ mod gc {
|
||||
continue;
|
||||
}
|
||||
if refers_to_target(obj.as_ref()) {
|
||||
result.push(obj.clone());
|
||||
result.push(obj);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -324,28 +324,33 @@ pub(crate) fn instrument_code(code: &Py<PyCode>, events: u32) {
|
||||
|
||||
// Phase 1: Remove INSTRUMENTED_LINE → restore from side-table
|
||||
if let Some(data) = monitoring_data.as_mut() {
|
||||
for i in 0..len {
|
||||
if data.line_opcodes[i] != 0 {
|
||||
let original = Instruction::try_from(data.line_opcodes[i])
|
||||
.expect("invalid opcode in line side-table");
|
||||
for (i, opcode) in data.line_opcodes.iter_mut().enumerate().take(len) {
|
||||
if *opcode != 0 {
|
||||
let original =
|
||||
Instruction::try_from(*opcode).expect("invalid opcode in line side-table");
|
||||
unsafe {
|
||||
code.code.instructions.replace_op(i, original);
|
||||
}
|
||||
data.line_opcodes[i] = 0;
|
||||
*opcode = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 2: Remove INSTRUMENTED_INSTRUCTION → restore from side-table
|
||||
if let Some(data) = monitoring_data.as_mut() {
|
||||
for i in 0..len {
|
||||
if data.per_instruction_opcodes[i] != 0 {
|
||||
let original = Instruction::try_from(data.per_instruction_opcodes[i])
|
||||
for (i, opcode) in data
|
||||
.per_instruction_opcodes
|
||||
.iter_mut()
|
||||
.enumerate()
|
||||
.take(len)
|
||||
{
|
||||
if *opcode != 0 {
|
||||
let original = Instruction::try_from(*opcode)
|
||||
.expect("invalid opcode in instruction side-table");
|
||||
unsafe {
|
||||
code.code.instructions.replace_op(i, original);
|
||||
}
|
||||
data.per_instruction_opcodes[i] = 0;
|
||||
*opcode = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -812,9 +817,9 @@ fn all_events(vm: &VirtualMachine) -> PyResult<PyDictRef> {
|
||||
.filter_map(|(event_id, event_name)| {
|
||||
let event_bit = 1u32 << event_id;
|
||||
let mut tools_mask = 0u8;
|
||||
for tool in 0..TOOL_LIMIT {
|
||||
if (state.global_events[tool] & event_bit) != 0 {
|
||||
tools_mask |= 1 << tool;
|
||||
for (i, tool) in state.global_events.iter().enumerate().take(TOOL_LIMIT) {
|
||||
if (tool & event_bit) != 0 {
|
||||
tools_mask |= 1 << i;
|
||||
}
|
||||
}
|
||||
if tools_mask != 0 {
|
||||
|
||||
@@ -8,6 +8,9 @@ rust-version.workspace = true
|
||||
repository.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
ascii = { workspace = true, features = ["alloc"] }
|
||||
bstr = { workspace = true, features = ["alloc"] }
|
||||
|
||||
@@ -67,7 +67,7 @@ impl Wtf8Concat for CodePoint {
|
||||
/// display output will be replaced with U+FFFD. Use direct [`Wtf8Concat`]
|
||||
/// impls for surrogate-preserving concatenation.
|
||||
#[allow(dead_code)]
|
||||
pub struct DisplayAsWtf8<T>(pub T);
|
||||
pub(crate) struct DisplayAsWtf8<T>(pub T);
|
||||
|
||||
impl<T: fmt::Display> Wtf8Concat for DisplayAsWtf8<T> {
|
||||
#[inline]
|
||||
@@ -112,9 +112,9 @@ impl<T: Wtf8Concat + ?Sized> Wtf8Concat for Box<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Wtf8Concat + ?Sized> Wtf8Concat for Cow<'_, T>
|
||||
impl<T> Wtf8Concat for Cow<'_, T>
|
||||
where
|
||||
T: ToOwned,
|
||||
T: ToOwned + Wtf8Concat + ?Sized,
|
||||
{
|
||||
#[inline]
|
||||
fn fmt_wtf8(&self, buf: &mut Wtf8Buf) {
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
|
||||
use core::slice;
|
||||
|
||||
pub const MAX_LEN_UTF8: usize = 4;
|
||||
pub const MAX_LEN_UTF16: usize = 2;
|
||||
pub(crate) const MAX_LEN_UTF8: usize = 4;
|
||||
pub(crate) const MAX_LEN_UTF16: usize = 2;
|
||||
|
||||
// UTF-8 ranges and tags for encoding characters
|
||||
const TAG_CONT: u8 = 0b1000_0000;
|
||||
@@ -17,7 +17,7 @@ const MAX_THREE_B: u32 = 0x10000;
|
||||
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub const fn len_utf8(code: u32) -> usize {
|
||||
pub(crate) const fn len_utf8(code: u32) -> usize {
|
||||
match code {
|
||||
..MAX_ONE_B => 1,
|
||||
..MAX_TWO_B => 2,
|
||||
@@ -47,7 +47,7 @@ const fn len_utf16(code: u32) -> usize {
|
||||
/// A buffer of length four is large enough to encode any `char`.
|
||||
#[doc(hidden)]
|
||||
#[inline]
|
||||
pub fn encode_utf8_raw(code: u32, dst: &mut [u8]) -> &mut [u8] {
|
||||
pub(crate) fn encode_utf8_raw(code: u32, dst: &mut [u8]) -> &mut [u8] {
|
||||
let len = len_utf8(code);
|
||||
match (len, &mut *dst) {
|
||||
(1, [a, ..]) => {
|
||||
@@ -91,7 +91,7 @@ pub fn encode_utf8_raw(code: u32, dst: &mut [u8]) -> &mut [u8] {
|
||||
/// A buffer of length 2 is large enough to encode any `char`.
|
||||
#[doc(hidden)]
|
||||
#[inline]
|
||||
pub fn encode_utf16_raw(mut code: u32, dst: &mut [u16]) -> &mut [u16] {
|
||||
pub(crate) fn encode_utf16_raw(mut code: u32, dst: &mut [u16]) -> &mut [u16] {
|
||||
let len = len_utf16(code);
|
||||
match (len, &mut *dst) {
|
||||
(1, [a, ..]) => {
|
||||
|
||||
@@ -30,7 +30,7 @@ pub(super) const fn utf8_is_cont_byte(byte: u8) -> bool {
|
||||
///
|
||||
/// `bytes` must produce a valid UTF-8-like (UTF-8 or WTF-8) string
|
||||
#[inline]
|
||||
pub unsafe fn next_code_point<'a, I: Iterator<Item = &'a u8>>(bytes: &mut I) -> Option<u32> {
|
||||
pub(crate) unsafe fn next_code_point<'a, I: Iterator<Item = &'a u8>>(bytes: &mut I) -> Option<u32> {
|
||||
// Decode UTF-8
|
||||
let x = *bytes.next()?;
|
||||
if x < 128 {
|
||||
@@ -73,7 +73,7 @@ pub unsafe fn next_code_point<'a, I: Iterator<Item = &'a u8>>(bytes: &mut I) ->
|
||||
///
|
||||
/// `bytes` must produce a valid UTF-8-like (UTF-8 or WTF-8) string
|
||||
#[inline]
|
||||
pub unsafe fn next_code_point_reverse<'a, I>(bytes: &mut I) -> Option<u32>
|
||||
pub(crate) unsafe fn next_code_point_reverse<'a, I>(bytes: &mut I) -> Option<u32>
|
||||
where
|
||||
I: DoubleEndedIterator<Item = &'a u8>,
|
||||
{
|
||||
|
||||
+135
-78
@@ -96,17 +96,19 @@ impl CodePoint {
|
||||
///
|
||||
/// `value` must be less than or equal to 0x10FFFF.
|
||||
#[inline]
|
||||
pub const unsafe fn from_u32_unchecked(value: u32) -> CodePoint {
|
||||
CodePoint { value }
|
||||
#[must_use]
|
||||
pub const unsafe fn from_u32_unchecked(value: u32) -> Self {
|
||||
Self { value }
|
||||
}
|
||||
|
||||
/// Creates a new `CodePoint` if the value is a valid code point.
|
||||
///
|
||||
/// Returns `None` if `value` is above 0x10FFFF.
|
||||
#[inline]
|
||||
pub const fn from_u32(value: u32) -> Option<CodePoint> {
|
||||
#[must_use]
|
||||
pub const fn from_u32(value: u32) -> Option<Self> {
|
||||
match value {
|
||||
0..=0x10FFFF => Some(CodePoint { value }),
|
||||
0..=0x10FFFF => Some(Self { value }),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -115,20 +117,23 @@ impl CodePoint {
|
||||
///
|
||||
/// Since all Unicode scalar values are code points, this always succeeds.
|
||||
#[inline]
|
||||
pub const fn from_char(value: char) -> CodePoint {
|
||||
CodePoint {
|
||||
#[must_use]
|
||||
pub const fn from_char(value: char) -> Self {
|
||||
Self {
|
||||
value: value as u32,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the numeric value of the code point.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub const fn to_u32(self) -> u32 {
|
||||
self.value
|
||||
}
|
||||
|
||||
/// Returns the numeric value of the code point if it is a leading surrogate.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub const fn to_lead_surrogate(self) -> Option<LeadSurrogate> {
|
||||
match self.value {
|
||||
lead @ 0xD800..=0xDBFF => Some(LeadSurrogate(lead as u16)),
|
||||
@@ -138,6 +143,7 @@ impl CodePoint {
|
||||
|
||||
/// Returns the numeric value of the code point if it is a trailing surrogate.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub const fn to_trail_surrogate(self) -> Option<TrailSurrogate> {
|
||||
match self.value {
|
||||
trail @ 0xDC00..=0xDFFF => Some(TrailSurrogate(trail as u16)),
|
||||
@@ -149,6 +155,7 @@ impl CodePoint {
|
||||
///
|
||||
/// Returns `None` if the code point is a surrogate (from U+D800 to U+DFFF).
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub const fn to_char(self) -> Option<char> {
|
||||
match self.value {
|
||||
0xD800..=0xDFFF => None,
|
||||
@@ -161,6 +168,7 @@ impl CodePoint {
|
||||
/// Returns `'\u{FFFD}'` (the replacement character “�”)
|
||||
/// if the code point is a surrogate (from U+D800 to U+DFFF).
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn to_char_lossy(self) -> char {
|
||||
self.to_char().unwrap_or('\u{FFFD}')
|
||||
}
|
||||
@@ -173,10 +181,12 @@ impl CodePoint {
|
||||
unsafe { Wtf8::from_mut_bytes_unchecked(encode_utf8_raw(self.value, dst)) }
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn len_wtf8(&self) -> usize {
|
||||
len_utf8(self.value)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn is_ascii(&self) -> bool {
|
||||
self.is_char_and(|c| c.is_ascii())
|
||||
}
|
||||
@@ -230,6 +240,7 @@ pub struct LeadSurrogate(u16);
|
||||
pub struct TrailSurrogate(u16);
|
||||
|
||||
impl LeadSurrogate {
|
||||
#[must_use]
|
||||
pub const fn merge(self, trail: TrailSurrogate) -> char {
|
||||
decode_surrogate_pair(self.0, trail.0)
|
||||
}
|
||||
@@ -288,14 +299,16 @@ impl fmt::Display for Wtf8Buf {
|
||||
impl Wtf8Buf {
|
||||
/// Creates a new, empty WTF-8 string.
|
||||
#[inline]
|
||||
pub fn new() -> Wtf8Buf {
|
||||
Wtf8Buf::default()
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Creates a new, empty WTF-8 string with pre-allocated capacity for `capacity` bytes.
|
||||
#[inline]
|
||||
pub fn with_capacity(capacity: usize) -> Wtf8Buf {
|
||||
Wtf8Buf {
|
||||
#[must_use]
|
||||
pub fn with_capacity(capacity: usize) -> Self {
|
||||
Self {
|
||||
bytes: Vec::with_capacity(capacity),
|
||||
}
|
||||
}
|
||||
@@ -306,8 +319,9 @@ impl Wtf8Buf {
|
||||
///
|
||||
/// `value` must contain valid WTF-8.
|
||||
#[inline]
|
||||
pub const unsafe fn from_bytes_unchecked(value: Vec<u8>) -> Wtf8Buf {
|
||||
Wtf8Buf { bytes: value }
|
||||
#[must_use]
|
||||
pub const unsafe fn from_bytes_unchecked(value: Vec<u8>) -> Self {
|
||||
Self { bytes: value }
|
||||
}
|
||||
|
||||
/// Create a WTF-8 string from a WTF-8 byte vec.
|
||||
@@ -324,13 +338,14 @@ impl Wtf8Buf {
|
||||
///
|
||||
/// Since WTF-8 is a superset of UTF-8, this always succeeds.
|
||||
#[inline]
|
||||
pub fn from_string(string: String) -> Wtf8Buf {
|
||||
Wtf8Buf {
|
||||
#[must_use]
|
||||
pub fn from_string(string: String) -> Self {
|
||||
Self {
|
||||
bytes: string.into_bytes(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn join<I, S>(sep: impl AsRef<Wtf8>, iter: I) -> Wtf8Buf
|
||||
pub fn join<I, S>(sep: impl AsRef<Wtf8>, iter: I) -> Self
|
||||
where
|
||||
I: IntoIterator<Item = S>,
|
||||
S: AsRef<Wtf8>,
|
||||
@@ -339,7 +354,7 @@ impl Wtf8Buf {
|
||||
let mut iter = iter.into_iter();
|
||||
let mut buf = match iter.next() {
|
||||
Some(first) => first.as_ref().to_owned(),
|
||||
None => return Wtf8Buf::new(),
|
||||
None => return Self::new(),
|
||||
};
|
||||
for part in iter {
|
||||
buf.push_wtf8(sep);
|
||||
@@ -356,9 +371,10 @@ impl Wtf8Buf {
|
||||
///
|
||||
/// This is lossless: calling `.encode_wide()` on the resulting string
|
||||
/// will always return the original code units.
|
||||
pub fn from_wide(v: &[u16]) -> Wtf8Buf {
|
||||
let mut string = Wtf8Buf::with_capacity(v.len());
|
||||
for item in char::decode_utf16(v.iter().cloned()) {
|
||||
#[must_use]
|
||||
pub fn from_wide(v: &[u16]) -> Self {
|
||||
let mut string = Self::with_capacity(v.len());
|
||||
for item in char::decode_utf16(v.iter().copied()) {
|
||||
match item {
|
||||
Ok(ch) => string.push_char(ch),
|
||||
Err(surrogate) => {
|
||||
@@ -375,6 +391,7 @@ impl Wtf8Buf {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn as_slice(&self) -> &Wtf8 {
|
||||
unsafe { Wtf8::from_bytes_unchecked(&self.bytes) }
|
||||
}
|
||||
@@ -452,12 +469,14 @@ impl Wtf8Buf {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn leak<'a>(self) -> &'a mut Wtf8 {
|
||||
unsafe { Wtf8::from_mut_bytes_unchecked(self.bytes.leak()) }
|
||||
}
|
||||
|
||||
/// Returns the number of bytes that this string buffer can hold without reallocating.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub const fn capacity(&self) -> usize {
|
||||
self.bytes.capacity()
|
||||
}
|
||||
@@ -521,6 +540,7 @@ impl Wtf8Buf {
|
||||
|
||||
/// Consumes the WTF-8 string and tries to convert it to a vec of bytes.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn into_bytes(self) -> Vec<u8> {
|
||||
self.bytes
|
||||
}
|
||||
@@ -532,7 +552,7 @@ impl Wtf8Buf {
|
||||
/// If the contents are not well-formed UTF-8
|
||||
/// (that is, if the string contains surrogates),
|
||||
/// the original WTF-8 string is returned instead.
|
||||
pub fn into_string(self) -> Result<String, Wtf8Buf> {
|
||||
pub fn into_string(self) -> Result<String, Self> {
|
||||
if self.is_utf8() {
|
||||
Ok(unsafe { String::from_utf8_unchecked(self.bytes) })
|
||||
} else {
|
||||
@@ -545,6 +565,7 @@ impl Wtf8Buf {
|
||||
/// This does not copy the data (but may overwrite parts of it in place).
|
||||
///
|
||||
/// Surrogates are replaced with `"\u{FFFD}"` (the replacement character “�”)
|
||||
#[must_use]
|
||||
pub fn into_string_lossy(mut self) -> String {
|
||||
let mut pos = 0;
|
||||
while let Some((surrogate_pos, _)) = self.next_surrogate(pos) {
|
||||
@@ -558,15 +579,17 @@ impl Wtf8Buf {
|
||||
|
||||
/// Converts this `Wtf8Buf` into a boxed `Wtf8`.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn into_box(self) -> Box<Wtf8> {
|
||||
// SAFETY: relies on `Wtf8` being `repr(transparent)`.
|
||||
unsafe { mem::transmute(self.bytes.into_boxed_slice()) }
|
||||
}
|
||||
|
||||
/// Converts a `Box<Wtf8>` into a `Wtf8Buf`.
|
||||
pub fn from_box(boxed: Box<Wtf8>) -> Wtf8Buf {
|
||||
#[must_use]
|
||||
pub fn from_box(boxed: Box<Wtf8>) -> Self {
|
||||
let bytes: Box<[u8]> = unsafe { mem::transmute(boxed) };
|
||||
Wtf8Buf {
|
||||
Self {
|
||||
bytes: bytes.into_vec(),
|
||||
}
|
||||
}
|
||||
@@ -577,8 +600,8 @@ impl Wtf8Buf {
|
||||
/// This replaces surrogate code point pairs with supplementary code points,
|
||||
/// like concatenating ill-formed UTF-16 strings effectively would.
|
||||
impl FromIterator<CodePoint> for Wtf8Buf {
|
||||
fn from_iter<T: IntoIterator<Item = CodePoint>>(iter: T) -> Wtf8Buf {
|
||||
let mut string = Wtf8Buf::new();
|
||||
fn from_iter<T: IntoIterator<Item = CodePoint>>(iter: T) -> Self {
|
||||
let mut string = Self::new();
|
||||
string.extend(iter);
|
||||
string
|
||||
}
|
||||
@@ -613,7 +636,7 @@ impl<W: AsRef<Wtf8>> Extend<W> for Wtf8Buf {
|
||||
|
||||
impl<W: AsRef<Wtf8>> FromIterator<W> for Wtf8Buf {
|
||||
fn from_iter<T: IntoIterator<Item = W>>(iter: T) -> Self {
|
||||
let mut buf = Wtf8Buf::new();
|
||||
let mut buf = Self::new();
|
||||
iter.into_iter().for_each(|w| buf.push_wtf8(w.as_ref()));
|
||||
buf
|
||||
}
|
||||
@@ -633,19 +656,19 @@ impl AsRef<Wtf8> for Wtf8Buf {
|
||||
|
||||
impl From<String> for Wtf8Buf {
|
||||
fn from(s: String) -> Self {
|
||||
Wtf8Buf::from_string(s)
|
||||
Self::from_string(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for Wtf8Buf {
|
||||
fn from(s: &str) -> Self {
|
||||
Wtf8Buf::from_string(s.to_owned())
|
||||
Self::from_string(s.to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ascii::AsciiString> for Wtf8Buf {
|
||||
fn from(s: ascii::AsciiString) -> Self {
|
||||
Wtf8Buf::from_string(s.into())
|
||||
Self::from_string(s.into())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -658,8 +681,8 @@ pub struct Wtf8 {
|
||||
bytes: [u8],
|
||||
}
|
||||
|
||||
impl AsRef<Wtf8> for Wtf8 {
|
||||
fn as_ref(&self) -> &Wtf8 {
|
||||
impl AsRef<Self> for Wtf8 {
|
||||
fn as_ref(&self) -> &Self {
|
||||
self
|
||||
}
|
||||
}
|
||||
@@ -730,9 +753,8 @@ impl fmt::Display for Wtf8 {
|
||||
let s = unsafe { str::from_utf8_unchecked(&wtf8_bytes[pos..]) };
|
||||
if pos == 0 {
|
||||
return s.fmt(formatter);
|
||||
} else {
|
||||
return formatter.write_str(s);
|
||||
}
|
||||
return formatter.write_str(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -757,7 +779,7 @@ impl Wtf8 {
|
||||
///
|
||||
/// Since WTF-8 is a superset of UTF-8, this always succeeds.
|
||||
#[inline]
|
||||
pub fn new<S: AsRef<Wtf8> + ?Sized>(value: &S) -> &Wtf8 {
|
||||
pub fn new<S: AsRef<Self> + ?Sized>(value: &S) -> &Self {
|
||||
value.as_ref()
|
||||
}
|
||||
|
||||
@@ -767,9 +789,10 @@ impl Wtf8 {
|
||||
///
|
||||
/// `value` must contain valid WTF-8.
|
||||
#[inline]
|
||||
pub const unsafe fn from_bytes_unchecked(value: &[u8]) -> &Wtf8 {
|
||||
#[must_use]
|
||||
pub const unsafe fn from_bytes_unchecked(value: &[u8]) -> &Self {
|
||||
// SAFETY: start with &[u8], end with fancy &[u8]
|
||||
unsafe { &*(value as *const [u8] as *const Wtf8) }
|
||||
unsafe { &*(value as *const [u8] as *const Self) }
|
||||
}
|
||||
|
||||
/// Creates a mutable WTF-8 slice from a mutable WTF-8 byte slice.
|
||||
@@ -777,15 +800,16 @@ impl Wtf8 {
|
||||
/// Since the byte slice is not checked for valid WTF-8, this functions is
|
||||
/// marked unsafe.
|
||||
#[inline]
|
||||
const unsafe fn from_mut_bytes_unchecked(value: &mut [u8]) -> &mut Wtf8 {
|
||||
const unsafe fn from_mut_bytes_unchecked(value: &mut [u8]) -> &mut Self {
|
||||
// SAFETY: start with &mut [u8], end with fancy &mut [u8]
|
||||
unsafe { &mut *(value as *mut [u8] as *mut Wtf8) }
|
||||
unsafe { &mut *(value as *mut [u8] as *mut Self) }
|
||||
}
|
||||
|
||||
/// Create a WTF-8 slice from a WTF-8 byte slice.
|
||||
//
|
||||
// whoops! using WTF-8 for interchange!
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn from_bytes(b: &[u8]) -> Option<&Self> {
|
||||
let mut rest = b;
|
||||
while let Err(e) = core::str::from_utf8(rest) {
|
||||
@@ -793,7 +817,7 @@ impl Wtf8 {
|
||||
let _ = Self::decode_surrogate(rest)?;
|
||||
rest = &rest[3..];
|
||||
}
|
||||
Some(unsafe { Wtf8::from_bytes_unchecked(b) })
|
||||
Some(unsafe { Self::from_bytes_unchecked(b) })
|
||||
}
|
||||
|
||||
fn decode_surrogate(b: &[u8]) -> Option<CodePoint> {
|
||||
@@ -805,11 +829,13 @@ impl Wtf8 {
|
||||
|
||||
/// Returns the length, in WTF-8 bytes.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub const fn len(&self) -> usize {
|
||||
self.bytes.len()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub const fn is_empty(&self) -> bool {
|
||||
self.bytes.is_empty()
|
||||
}
|
||||
@@ -821,6 +847,7 @@ impl Wtf8 {
|
||||
///
|
||||
/// Panics if `position` is beyond the end of the string.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub const fn ascii_byte_at(&self, position: usize) -> u8 {
|
||||
match self.bytes[position] {
|
||||
ascii_byte @ 0x00..=0x7F => ascii_byte,
|
||||
@@ -830,6 +857,7 @@ impl Wtf8 {
|
||||
|
||||
/// Returns an iterator for the string’s code points.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn code_points(&self) -> Wtf8CodePoints<'_> {
|
||||
Wtf8CodePoints {
|
||||
bytes: self.bytes.iter(),
|
||||
@@ -838,6 +866,7 @@ impl Wtf8 {
|
||||
|
||||
/// Returns an iterator for the string’s code points and their indices.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn code_point_indices(&self) -> Wtf8CodePointIndices<'_> {
|
||||
Wtf8CodePointIndices {
|
||||
front_offset: 0,
|
||||
@@ -847,6 +876,7 @@ impl Wtf8 {
|
||||
|
||||
/// Access raw bytes of WTF-8 data
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub const fn as_bytes(&self) -> &[u8] {
|
||||
&self.bytes
|
||||
}
|
||||
@@ -862,6 +892,7 @@ impl Wtf8 {
|
||||
}
|
||||
|
||||
/// Creates an owned `Wtf8Buf` from a borrowed `Wtf8`.
|
||||
#[must_use]
|
||||
pub fn to_wtf8_buf(&self) -> Wtf8Buf {
|
||||
Wtf8Buf {
|
||||
bytes: self.bytes.to_vec(),
|
||||
@@ -874,6 +905,7 @@ impl Wtf8 {
|
||||
/// Surrogates are replaced with `"\u{FFFD}"` (the replacement character “�”).
|
||||
///
|
||||
/// This only copies the data if necessary (if it contains any surrogate).
|
||||
#[must_use]
|
||||
pub fn to_string_lossy(&self) -> Cow<'_, str> {
|
||||
let Some((surrogate_pos, _)) = self.next_surrogate(0) else {
|
||||
return Cow::Borrowed(unsafe { str::from_utf8_unchecked(&self.bytes) });
|
||||
@@ -905,6 +937,7 @@ impl Wtf8 {
|
||||
/// calling `Wtf8Buf::from_ill_formed_utf16` on the resulting code units
|
||||
/// would always return the original WTF-8 string.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn encode_wide(&self) -> EncodeWide<'_> {
|
||||
EncodeWide {
|
||||
code_points: self.code_points(),
|
||||
@@ -912,6 +945,7 @@ impl Wtf8 {
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn chunks(&self) -> Wtf8Chunks<'_> {
|
||||
Wtf8Chunks { wtf8: self }
|
||||
}
|
||||
@@ -956,19 +990,22 @@ impl Wtf8 {
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn is_code_point_boundary(&self, index: usize) -> bool {
|
||||
is_code_point_boundary(self, index)
|
||||
}
|
||||
|
||||
/// Boxes this `Wtf8`.
|
||||
#[inline]
|
||||
pub fn into_box(&self) -> Box<Wtf8> {
|
||||
#[must_use]
|
||||
pub fn into_box(&self) -> Box<Self> {
|
||||
let boxed: Box<[u8]> = self.bytes.into();
|
||||
unsafe { mem::transmute(boxed) }
|
||||
}
|
||||
|
||||
/// Creates a boxed, empty `Wtf8`.
|
||||
pub fn empty_box() -> Box<Wtf8> {
|
||||
#[must_use]
|
||||
pub fn empty_box() -> Box<Self> {
|
||||
let boxed: Box<[u8]> = Default::default();
|
||||
unsafe { mem::transmute(boxed) }
|
||||
}
|
||||
@@ -984,6 +1021,7 @@ impl Wtf8 {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn to_ascii_lowercase(&self) -> Wtf8Buf {
|
||||
Wtf8Buf {
|
||||
bytes: self.bytes.to_ascii_lowercase(),
|
||||
@@ -991,12 +1029,14 @@ impl Wtf8 {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn to_ascii_uppercase(&self) -> Wtf8Buf {
|
||||
Wtf8Buf {
|
||||
bytes: self.bytes.to_ascii_uppercase(),
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn to_lowercase(&self) -> Wtf8Buf {
|
||||
let mut buf = Wtf8Buf::with_capacity(self.len());
|
||||
for chunk in self.chunks() {
|
||||
@@ -1008,6 +1048,7 @@ impl Wtf8 {
|
||||
buf
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn to_uppercase(&self) -> Wtf8Buf {
|
||||
let mut buf = Wtf8Buf::with_capacity(self.len());
|
||||
for chunk in self.chunks() {
|
||||
@@ -1020,57 +1061,63 @@ impl Wtf8 {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub const fn is_ascii(&self) -> bool {
|
||||
self.bytes.is_ascii()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn is_utf8(&self) -> bool {
|
||||
self.next_surrogate(0).is_none()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn eq_ignore_ascii_case(&self, other: &Self) -> bool {
|
||||
self.bytes.eq_ignore_ascii_case(&other.bytes)
|
||||
}
|
||||
|
||||
pub fn split(&self, pat: &Wtf8) -> impl Iterator<Item = &Self> {
|
||||
pub fn split(&self, pat: &Self) -> impl Iterator<Item = &Self> {
|
||||
self.as_bytes()
|
||||
.split_str(pat)
|
||||
.map(|w| unsafe { Wtf8::from_bytes_unchecked(w) })
|
||||
.map(|w| unsafe { Self::from_bytes_unchecked(w) })
|
||||
}
|
||||
|
||||
pub fn splitn(&self, n: usize, pat: &Wtf8) -> impl Iterator<Item = &Self> {
|
||||
pub fn splitn(&self, n: usize, pat: &Self) -> impl Iterator<Item = &Self> {
|
||||
self.as_bytes()
|
||||
.splitn_str(n, pat)
|
||||
.map(|w| unsafe { Wtf8::from_bytes_unchecked(w) })
|
||||
.map(|w| unsafe { Self::from_bytes_unchecked(w) })
|
||||
}
|
||||
|
||||
pub fn rsplit(&self, pat: &Wtf8) -> impl Iterator<Item = &Self> {
|
||||
pub fn rsplit(&self, pat: &Self) -> impl Iterator<Item = &Self> {
|
||||
self.as_bytes()
|
||||
.rsplit_str(pat)
|
||||
.map(|w| unsafe { Wtf8::from_bytes_unchecked(w) })
|
||||
.map(|w| unsafe { Self::from_bytes_unchecked(w) })
|
||||
}
|
||||
|
||||
pub fn rsplitn(&self, n: usize, pat: &Wtf8) -> impl Iterator<Item = &Self> {
|
||||
pub fn rsplitn(&self, n: usize, pat: &Self) -> impl Iterator<Item = &Self> {
|
||||
self.as_bytes()
|
||||
.rsplitn_str(n, pat)
|
||||
.map(|w| unsafe { Wtf8::from_bytes_unchecked(w) })
|
||||
.map(|w| unsafe { Self::from_bytes_unchecked(w) })
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn trim(&self) -> &Self {
|
||||
let w = self.bytes.trim();
|
||||
unsafe { Wtf8::from_bytes_unchecked(w) }
|
||||
unsafe { Self::from_bytes_unchecked(w) }
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn trim_start(&self) -> &Self {
|
||||
let w = self.bytes.trim_start();
|
||||
unsafe { Wtf8::from_bytes_unchecked(w) }
|
||||
unsafe { Self::from_bytes_unchecked(w) }
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn trim_end(&self) -> &Self {
|
||||
let w = self.bytes.trim_end();
|
||||
unsafe { Wtf8::from_bytes_unchecked(w) }
|
||||
unsafe { Self::from_bytes_unchecked(w) }
|
||||
}
|
||||
|
||||
pub fn trim_start_matches(&self, f: impl Fn(CodePoint) -> bool) -> &Self {
|
||||
@@ -1109,26 +1156,30 @@ impl Wtf8 {
|
||||
self.trim_start_matches(&f).trim_end_matches(&f)
|
||||
}
|
||||
|
||||
pub fn find(&self, pat: &Wtf8) -> Option<usize> {
|
||||
#[must_use]
|
||||
pub fn find(&self, pat: &Self) -> Option<usize> {
|
||||
memchr::memmem::find(self.as_bytes(), pat.as_bytes())
|
||||
}
|
||||
|
||||
pub fn rfind(&self, pat: &Wtf8) -> Option<usize> {
|
||||
#[must_use]
|
||||
pub fn rfind(&self, pat: &Self) -> Option<usize> {
|
||||
memchr::memmem::rfind(self.as_bytes(), pat.as_bytes())
|
||||
}
|
||||
|
||||
pub fn find_iter(&self, pat: &Wtf8) -> impl Iterator<Item = usize> {
|
||||
pub fn find_iter(&self, pat: &Self) -> impl Iterator<Item = usize> {
|
||||
memchr::memmem::find_iter(self.as_bytes(), pat.as_bytes())
|
||||
}
|
||||
|
||||
pub fn rfind_iter(&self, pat: &Wtf8) -> impl Iterator<Item = usize> {
|
||||
pub fn rfind_iter(&self, pat: &Self) -> impl Iterator<Item = usize> {
|
||||
memchr::memmem::rfind_iter(self.as_bytes(), pat.as_bytes())
|
||||
}
|
||||
|
||||
pub fn contains(&self, pat: &Wtf8) -> bool {
|
||||
#[must_use]
|
||||
pub fn contains(&self, pat: &Self) -> bool {
|
||||
self.bytes.contains_str(pat)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn contains_code_point(&self, pat: CodePoint) -> bool {
|
||||
self.bytes
|
||||
.contains_str(pat.encode_wtf8(&mut [0; MAX_LEN_UTF8]))
|
||||
@@ -1154,27 +1205,28 @@ impl Wtf8 {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ends_with(&self, w: impl AsRef<Wtf8>) -> bool {
|
||||
pub fn ends_with(&self, w: impl AsRef<Self>) -> bool {
|
||||
self.bytes.ends_with_str(w.as_ref())
|
||||
}
|
||||
|
||||
pub fn starts_with(&self, w: impl AsRef<Wtf8>) -> bool {
|
||||
pub fn starts_with(&self, w: impl AsRef<Self>) -> bool {
|
||||
self.bytes.starts_with_str(w.as_ref())
|
||||
}
|
||||
|
||||
pub fn strip_prefix(&self, w: impl AsRef<Wtf8>) -> Option<&Self> {
|
||||
pub fn strip_prefix(&self, w: impl AsRef<Self>) -> Option<&Self> {
|
||||
self.bytes
|
||||
.strip_prefix(w.as_ref().as_bytes())
|
||||
.map(|w| unsafe { Wtf8::from_bytes_unchecked(w) })
|
||||
.map(|w| unsafe { Self::from_bytes_unchecked(w) })
|
||||
}
|
||||
|
||||
pub fn strip_suffix(&self, w: impl AsRef<Wtf8>) -> Option<&Self> {
|
||||
pub fn strip_suffix(&self, w: impl AsRef<Self>) -> Option<&Self> {
|
||||
self.bytes
|
||||
.strip_suffix(w.as_ref().as_bytes())
|
||||
.map(|w| unsafe { Wtf8::from_bytes_unchecked(w) })
|
||||
.map(|w| unsafe { Self::from_bytes_unchecked(w) })
|
||||
}
|
||||
|
||||
pub fn replace(&self, from: &Wtf8, to: &Wtf8) -> Wtf8Buf {
|
||||
#[must_use]
|
||||
pub fn replace(&self, from: &Self, to: &Self) -> Wtf8Buf {
|
||||
if from.is_empty() {
|
||||
return self.insert_at_boundaries(to, usize::MAX);
|
||||
}
|
||||
@@ -1182,7 +1234,8 @@ impl Wtf8 {
|
||||
unsafe { Wtf8Buf::from_bytes_unchecked(w) }
|
||||
}
|
||||
|
||||
pub fn replacen(&self, from: &Wtf8, to: &Wtf8, n: usize) -> Wtf8Buf {
|
||||
#[must_use]
|
||||
pub fn replacen(&self, from: &Self, to: &Self, n: usize) -> Wtf8Buf {
|
||||
if from.is_empty() {
|
||||
return self.insert_at_boundaries(to, n);
|
||||
}
|
||||
@@ -1198,7 +1251,7 @@ impl Wtf8 {
|
||||
/// *byte*, so it splits a multi-byte code point down the middle and leaves
|
||||
/// bytes that are no longer WTF-8 at all. A needle that is not empty is
|
||||
/// safe there, because a WTF-8 sequence never starts inside another one.
|
||||
fn insert_at_boundaries(&self, to: &Wtf8, limit: usize) -> Wtf8Buf {
|
||||
fn insert_at_boundaries(&self, to: &Self, limit: usize) -> Wtf8Buf {
|
||||
let mut result = Wtf8Buf::with_capacity(self.len());
|
||||
let mut inserted = 0;
|
||||
|
||||
@@ -1237,11 +1290,11 @@ impl AsRef<[u8]> for Wtf8 {
|
||||
/// Panics when `begin` and `end` do not point to code point boundaries,
|
||||
/// or point beyond the end of the string.
|
||||
impl ops::Index<ops::Range<usize>> for Wtf8 {
|
||||
type Output = Wtf8;
|
||||
type Output = Self;
|
||||
|
||||
#[inline]
|
||||
#[track_caller]
|
||||
fn index(&self, range: ops::Range<usize>) -> &Wtf8 {
|
||||
fn index(&self, range: ops::Range<usize>) -> &Self {
|
||||
// is_code_point_boundary checks that the index is in [0, .len()]
|
||||
if range.start <= range.end
|
||||
&& is_code_point_boundary(self, range.start)
|
||||
@@ -1261,11 +1314,11 @@ impl ops::Index<ops::Range<usize>> for Wtf8 {
|
||||
/// Panics when `begin` is not at a code point boundary,
|
||||
/// or is beyond the end of the string.
|
||||
impl ops::Index<ops::RangeFrom<usize>> for Wtf8 {
|
||||
type Output = Wtf8;
|
||||
type Output = Self;
|
||||
|
||||
#[inline]
|
||||
#[track_caller]
|
||||
fn index(&self, range: ops::RangeFrom<usize>) -> &Wtf8 {
|
||||
fn index(&self, range: ops::RangeFrom<usize>) -> &Self {
|
||||
// is_code_point_boundary checks that the index is in [0, .len()]
|
||||
if is_code_point_boundary(self, range.start) {
|
||||
unsafe { slice_unchecked(self, range.start, self.len()) }
|
||||
@@ -1282,11 +1335,11 @@ impl ops::Index<ops::RangeFrom<usize>> for Wtf8 {
|
||||
/// Panics when `end` is not at a code point boundary,
|
||||
/// or is beyond the end of the string.
|
||||
impl ops::Index<ops::RangeTo<usize>> for Wtf8 {
|
||||
type Output = Wtf8;
|
||||
type Output = Self;
|
||||
|
||||
#[inline]
|
||||
#[track_caller]
|
||||
fn index(&self, range: ops::RangeTo<usize>) -> &Wtf8 {
|
||||
fn index(&self, range: ops::RangeTo<usize>) -> &Self {
|
||||
// is_code_point_boundary checks that the index is in [0, .len()]
|
||||
if is_code_point_boundary(self, range.end) {
|
||||
unsafe { slice_unchecked(self, 0, range.end) }
|
||||
@@ -1297,10 +1350,10 @@ impl ops::Index<ops::RangeTo<usize>> for Wtf8 {
|
||||
}
|
||||
|
||||
impl ops::Index<ops::RangeFull> for Wtf8 {
|
||||
type Output = Wtf8;
|
||||
type Output = Self;
|
||||
|
||||
#[inline]
|
||||
fn index(&self, _range: ops::RangeFull) -> &Wtf8 {
|
||||
fn index(&self, _range: ops::RangeFull) -> &Self {
|
||||
self
|
||||
}
|
||||
}
|
||||
@@ -1351,9 +1404,10 @@ pub fn check_utf8_boundary(slice: &Wtf8, index: usize) {
|
||||
}
|
||||
if slice.bytes[index + 1] >= 0xA0 {
|
||||
// There's a surrogate after index. Now check before index.
|
||||
if index >= 3 && slice.bytes[index - 3] == 0xED && slice.bytes[index - 2] >= 0xA0 {
|
||||
panic!("byte index {index} lies between surrogate codepoints");
|
||||
}
|
||||
assert!(
|
||||
!(index >= 3 && slice.bytes[index - 3] == 0xED && slice.bytes[index - 2] >= 0xA0),
|
||||
"byte index {index} lies between surrogate codepoints"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1363,6 +1417,7 @@ pub fn check_utf8_boundary(slice: &Wtf8, index: usize) {
|
||||
///
|
||||
/// `begin` and `end` must be within bounds and on codepoint boundaries.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub const unsafe fn slice_unchecked(s: &Wtf8, begin: usize, end: usize) -> &Wtf8 {
|
||||
// SAFETY: memory layout of a &[u8] and &Wtf8 are the same
|
||||
unsafe {
|
||||
@@ -1431,6 +1486,7 @@ impl DoubleEndedIterator for Wtf8CodePoints<'_> {
|
||||
}
|
||||
|
||||
impl<'a> Wtf8CodePoints<'a> {
|
||||
#[must_use]
|
||||
pub fn as_wtf8(&self) -> &'a Wtf8 {
|
||||
unsafe { Wtf8::from_bytes_unchecked(self.bytes.as_slice()) }
|
||||
}
|
||||
@@ -1578,6 +1634,7 @@ impl Hash for CodePoint {
|
||||
/// # Safety
|
||||
///
|
||||
/// `value` must be valid WTF-8.
|
||||
#[must_use]
|
||||
pub unsafe fn from_boxed_wtf8_unchecked(value: Box<[u8]>) -> Box<Wtf8> {
|
||||
unsafe { Box::from_raw(Box::into_raw(value) as *mut Wtf8) }
|
||||
}
|
||||
@@ -1628,7 +1685,7 @@ impl From<Box<ascii::AsciiStr>> for Box<Wtf8> {
|
||||
|
||||
impl From<Box<Wtf8>> for Box<[u8]> {
|
||||
fn from(w: Box<Wtf8>) -> Self {
|
||||
unsafe { Box::from_raw(Box::into_raw(w) as *mut [u8]) }
|
||||
unsafe { Self::from_raw(Box::into_raw(w) as *mut [u8]) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1640,7 +1697,7 @@ impl From<Wtf8Buf> for Box<Wtf8> {
|
||||
|
||||
impl From<Box<Wtf8>> for Wtf8Buf {
|
||||
fn from(w: Box<Wtf8>) -> Self {
|
||||
Wtf8Buf::from_box(w)
|
||||
Self::from_box(w)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ mod rust_py_module {
|
||||
s: String,
|
||||
python_person: PythonPerson,
|
||||
_vm: &VirtualMachine,
|
||||
) -> PyResult<RustStruct> {
|
||||
) -> RustStruct {
|
||||
println!(
|
||||
"Calling standalone rust function from python passing args:
|
||||
num: {},
|
||||
@@ -42,9 +42,9 @@ string: {},
|
||||
python_person.name: {}",
|
||||
num, s, python_person.name
|
||||
);
|
||||
Ok(RustStruct {
|
||||
RustStruct {
|
||||
numbers: NumVec(vec![1, 2, 3, 4]),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
||||
@@ -4,6 +4,7 @@ fn main() -> vm::PyResult<()> {
|
||||
vm::Interpreter::without_stdlib(Default::default()).enter(run)
|
||||
}
|
||||
|
||||
#[expect(clippy::unnecessary_wraps)]
|
||||
fn run(vm: &vm::VirtualMachine) -> vm::PyResult<()> {
|
||||
let scope = vm.new_scope_with_builtins();
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
use rustpython_vm as vm;
|
||||
// these are needed for special memory shenanigans to let us share a variable with Python and Rust
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use core::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
// This has to be a macro because it uses the py_compile macro,
|
||||
// which compiles python source to optimized bytecode at compile time, so that
|
||||
|
||||
@@ -8,11 +8,12 @@ extern crate env_logger;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
use core::time::Duration;
|
||||
use ruff_python_parser::parse_module;
|
||||
use rustpython_compiler::ast;
|
||||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
time::{Duration, Instant},
|
||||
time::Instant,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
|
||||
@@ -6,6 +6,7 @@ pub trait InterpreterBuilderExt {
|
||||
///
|
||||
/// Requires the `stdlib` feature to be enabled.
|
||||
#[cfg(feature = "stdlib")]
|
||||
#[must_use]
|
||||
fn init_stdlib(self) -> Self;
|
||||
}
|
||||
|
||||
|
||||
+3
-2
@@ -84,6 +84,7 @@ compile_error!(
|
||||
/// **Note**: This function provides no way to further initialize the VM after the builder is applied.
|
||||
/// All VM initialization (adding native modules, init hooks, etc.) must be done through the
|
||||
/// [`InterpreterBuilder`] parameter before calling this function.
|
||||
#[must_use]
|
||||
pub fn run(mut builder: InterpreterBuilder) -> ExitCode {
|
||||
env_logger::init();
|
||||
|
||||
@@ -369,7 +370,7 @@ fn run_rustpython(vm: &VirtualMachine, run_mode: RunMode) -> PyResult<()> {
|
||||
RunMode::InstallPip(installer) => install_pip(installer, scope.clone(), vm),
|
||||
RunMode::Script(script_path) => {
|
||||
// pymain_run_file_obj
|
||||
debug!("Running script {}", script_path);
|
||||
debug!("Running script {script_path}");
|
||||
run_file(vm, scope.clone(), &script_path)
|
||||
}
|
||||
RunMode::Repl => Ok(()),
|
||||
@@ -467,7 +468,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_run_script() {
|
||||
fn run_script() {
|
||||
interpreter().enter(|vm| {
|
||||
vm.unwrap_pyresult((|| {
|
||||
let scope = vm.new_scope_with_main()?;
|
||||
|
||||
@@ -4,6 +4,7 @@ use rustpython::{InterpreterBuilder, InterpreterBuilderExt};
|
||||
#[global_allocator]
|
||||
static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
|
||||
|
||||
#[must_use]
|
||||
pub fn main() -> std::process::ExitCode {
|
||||
let mut config = InterpreterBuilder::new();
|
||||
#[cfg(feature = "stdlib")]
|
||||
|
||||
+7
-6
@@ -1,9 +1,10 @@
|
||||
use lexopt::Arg::*;
|
||||
use core::cmp;
|
||||
use core::num::NonZeroI32;
|
||||
use core::str::FromStr;
|
||||
use lexopt::Arg::{Long, Short, Value};
|
||||
use lexopt::ValueExt;
|
||||
use rustpython_vm::{Settings, vm::CheckHashPycsMode};
|
||||
use std::num::NonZeroI32;
|
||||
use std::str::FromStr;
|
||||
use std::{cmp, env};
|
||||
use std::env;
|
||||
|
||||
pub enum RunMode {
|
||||
Script(String),
|
||||
@@ -113,7 +114,7 @@ fn parse_args() -> Result<(CliArgs, RunMode, Vec<String>), lexopt::Error> {
|
||||
let mut args = CliArgs::default();
|
||||
let mut parser = lexopt::Parser::from_env();
|
||||
fn argv(argv0: String, mut parser: lexopt::Parser) -> Result<Vec<String>, lexopt::Error> {
|
||||
std::iter::once(Ok(argv0))
|
||||
core::iter::once(Ok(argv0))
|
||||
.chain(parser.raw_args()?.map(|arg| arg.string()))
|
||||
.collect()
|
||||
}
|
||||
@@ -532,7 +533,7 @@ fn dedent(input: &str) -> String {
|
||||
.collect();
|
||||
|
||||
// Find maximum common whitespace prefix, if any.
|
||||
for line in deblanked.iter() {
|
||||
for line in &deblanked {
|
||||
if line.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
+3
-3
@@ -6,7 +6,7 @@ use rustpython_vm::{
|
||||
identifier,
|
||||
};
|
||||
|
||||
pub struct ShellHelper<'vm> {
|
||||
pub(super) struct ShellHelper<'vm> {
|
||||
vm: &'vm VirtualMachine,
|
||||
globals: PyDictRef,
|
||||
}
|
||||
@@ -54,7 +54,7 @@ fn split_idents_on_dot(line: &str) -> Option<(usize, Vec<String>)> {
|
||||
}
|
||||
|
||||
impl<'vm> ShellHelper<'vm> {
|
||||
pub const fn new(vm: &'vm VirtualMachine, globals: PyDictRef) -> Self {
|
||||
pub(super) const fn new(vm: &'vm VirtualMachine, globals: PyDictRef) -> Self {
|
||||
ShellHelper { vm, globals }
|
||||
}
|
||||
|
||||
@@ -159,7 +159,7 @@ cfg_select! {
|
||||
&self,
|
||||
line: &str,
|
||||
pos: usize,
|
||||
_ctx: &Context,
|
||||
_ctx: &Context<'_>,
|
||||
) -> rustyline::Result<(usize, Vec<String>)> {
|
||||
Ok(self
|
||||
.complete_opt(&line[0..pos])
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
// cspell:ignore pyperformance nqueens pidigits
|
||||
|
||||
#![allow(clippy::tests_outside_test_module)]
|
||||
|
||||
use rustpython::{InterpreterBuilder, InterpreterBuilderExt};
|
||||
use std::{fs, path::Path};
|
||||
|
||||
|
||||
Reference in new issue
Block a user