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:
Joshua Megnauth authored and GitHub committed 2026-09-24 07:49:50 +09:00
1 parent 30ff0329a2
commit 86c7fa20c4
37 files changed
+371 -259

No files matched your search

+3
View File
@@ -430,3 +430,6 @@ unnecessary_box_returns = "warn"
unnecessary_join = "warn"
unnecessary_wraps = "warn"
unnested_or_patterns = "warn"
[lints]
workspace = true
+21 -8
View File
@@ -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}");
+9 -12
View File
@@ -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");
+7 -6
View File
@@ -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;
+3 -4
View File
@@ -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);
+3 -3
View File
@@ -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);
+3 -3
View File
@@ -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"] }
+2 -1
View File
@@ -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 }
}
-1
View File
@@ -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)]
{
+3
View File
@@ -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 }
+2 -2
View File
@@ -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)
};
+17
View File
@@ -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
View File
@@ -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");
}
+2 -2
View File
@@ -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;
}
}
-2
View File
@@ -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],
}
+1 -1
View File
@@ -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())
+8 -8
View File
@@ -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"))?;
-1
View File
@@ -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"))?;
+1 -1
View File
@@ -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;
+1 -1
View File
@@ -238,7 +238,7 @@ mod gc {
continue;
}
if refers_to_target(obj.as_ref()) {
result.push(obj.clone());
result.push(obj);
}
}
+17 -12
View File
@@ -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 {
+3
View File
@@ -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"] }
+3 -3
View File
@@ -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) {
+5 -5
View File
@@ -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, ..]) => {
+2 -2
View File
@@ -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
View File
@@ -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)
}
}
+3 -3
View File
@@ -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)]
+1
View File
@@ -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();
+1 -1
View File
@@ -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
+2 -1
View File
@@ -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() {
+1
View File
@@ -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
View File
@@ -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()?;
+1
View File
@@ -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
View File
@@ -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
View File
@@ -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])
+3
View File
@@ -1,4 +1,7 @@
// cspell:ignore pyperformance nqueens pidigits
#![allow(clippy::tests_outside_test_module)]
use rustpython::{InterpreterBuilder, InterpreterBuilderExt};
use std::{fs, path::Path};