mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 10:44:36 +08:00
Align codec, call, exception, and Unix module behavior with CPython (#8647)
* Collect consecutive charmap encoding failures Pass one contiguous run of unmappable characters to the codec error handler, and use the full run when a text replacement is itself unmappable. Assisted-by: Codex:gpt-5 * Leave conversions out of standalone format specs A conversion belongs to a replacement field rather than the format specification consumed by format(), so stop accepting and storing it in FormatSpec. Assisted-by: Codex:gpt-5 * Decode empty byte strings without looking up a codec Match PyUnicode_FromEncodedObject by returning the shared empty string before resolving the requested encoding for empty bytes and bytearray inputs. Assisted-by: Codex:gpt-5 * Validate call shape before converting builtin arguments Reject unknown keywords and excess arguments before conversions can raise a different error. Align Python-function positional-only handling, use declared list arguments in heapq, keep delegated codecs positional-only, and preserve CPython parameter names and validation order. Assisted-by: Codex:gpt-5 * Align attribute and exception initialization errors Give __dict__ and type __qualname__ their CPython diagnostics, preserve OSError filename fields correctly, accept seven-item SyntaxError details, and report Unicode error constructor arity before converting values. Assisted-by: Codex:gpt-5 * Expose missing Unix and Darwin module APIs Add the platform constants exported by fcntl, mmap, select, socket, syslog, termios, stat, errno, time, and _sre. Implement posix.killpg and resource.getpagesize, and expose resource.error as OSError. Assisted-by: Codex:gpt-5 * Honor the configured Python CPU count Parse -X cpu_count and PYTHON_CPU_COUNT, expose the stored value through sys._get_cpu_count_config, and let os.cpu_count prefer it over the host count. Also publish getsizeof with its CPython parameter name. Assisted-by: Codex:gpt-5 * Fix CPU count and WASI CI coverage Remove the obsolete expected-failure marker now that CPU count overrides pass, and avoid exporting FASYNC where WASI libc has no O_ASYNC constant. Assisted-by: Codex:gpt-5 * self review
This commit is contained in:
1 parent
9b5e550f51
commit
699913d8cd
38 files changed
+593
-331
No files matched your search
@@ -293,3 +293,4 @@ XSETREF
|
||||
xibufferview
|
||||
xstat
|
||||
XXPRIME
|
||||
ENOTCAPABLE
|
||||
Vendored
+37
-22
@@ -1687,6 +1687,39 @@ def charmapencode_output(c, mapping):
|
||||
raise TypeError("character mapping must return integer, None or str")
|
||||
|
||||
|
||||
def charmap_encoding_error(p, size, inpos, mapping, errors, res):
|
||||
# /* find all unencodable characters */
|
||||
collendpos = inpos + 1
|
||||
while collendpos < size:
|
||||
try:
|
||||
charmapencode_output(ord(p[collendpos]), mapping)
|
||||
except KeyError:
|
||||
collendpos += 1
|
||||
else:
|
||||
break
|
||||
|
||||
replacement, newpos = unicode_call_errorhandler(
|
||||
errors,
|
||||
"charmap",
|
||||
"character maps to <undefined>",
|
||||
p,
|
||||
inpos,
|
||||
collendpos,
|
||||
False,
|
||||
)
|
||||
if isinstance(replacement, bytes):
|
||||
res += list(replacement)
|
||||
else:
|
||||
for y in replacement:
|
||||
try:
|
||||
res += charmapencode_output(ord(y), mapping)
|
||||
except KeyError:
|
||||
raise UnicodeEncodeError(
|
||||
"charmap", p, inpos, collendpos, "character maps to <undefined>"
|
||||
)
|
||||
return newpos
|
||||
|
||||
|
||||
def PyUnicode_EncodeCharmap(p, size, mapping="latin-1", errors="strict"):
|
||||
## /* the following variable is used for caching string comparisons
|
||||
## * -1=not initialized, 0=unknown, 1=strict, 2=replace,
|
||||
@@ -1703,29 +1736,11 @@ def PyUnicode_EncodeCharmap(p, size, mapping="latin-1", errors="strict"):
|
||||
# /* try to encode it */
|
||||
try:
|
||||
x = charmapencode_output(ord(p[inpos]), mapping)
|
||||
res += x
|
||||
except KeyError:
|
||||
x = unicode_call_errorhandler(
|
||||
errors,
|
||||
"charmap",
|
||||
"character maps to <undefined>",
|
||||
p,
|
||||
inpos,
|
||||
inpos + 1,
|
||||
False,
|
||||
)
|
||||
replacement = x[0]
|
||||
if isinstance(replacement, bytes):
|
||||
res += list(replacement)
|
||||
else:
|
||||
try:
|
||||
for y in replacement:
|
||||
res += charmapencode_output(ord(y), mapping)
|
||||
except KeyError:
|
||||
raise UnicodeEncodeError(
|
||||
"charmap", p, inpos, inpos + 1, "character maps to <undefined>"
|
||||
)
|
||||
inpos += 1
|
||||
inpos = charmap_encoding_error(p, size, inpos, mapping, errors, res)
|
||||
else:
|
||||
res += x
|
||||
inpos += 1
|
||||
return res
|
||||
|
||||
|
||||
|
||||
Vendored
-1
@@ -1192,7 +1192,6 @@ class CmdLineTest(unittest.TestCase):
|
||||
code = template.replace('-', '\t').replace('+', ' ')
|
||||
assert_python_failure('-c', code)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_cpu_count(self):
|
||||
code = "import os; print(os.cpu_count(), os.process_cpu_count())"
|
||||
res = assert_python_ok('-X', 'cpu_count=4321', '-c', code)
|
||||
|
||||
-1
@@ -2274,7 +2274,6 @@ class TestGetcallargsFunctions(unittest.TestCase):
|
||||
'(4,[5,6])]), q=0, **collections.UserDict('
|
||||
'y=9, z=10)')
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; + <lambda>() got an unexpected keyword argument 'x'
|
||||
def test_errors(self):
|
||||
f0 = self.makeCallable('')
|
||||
f1 = self.makeCallable('a, b')
|
||||
|
||||
Vendored
-1
@@ -197,7 +197,6 @@ class ResourceTest(unittest.TestCase):
|
||||
|
||||
resource.setrlimit(resource.RLIMIT_CPU, BadSequence())
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; module 'resource' has no attribute 'getpagesize'
|
||||
def test_pagesize(self):
|
||||
pagesize = resource.getpagesize()
|
||||
self.assertIsInstance(pagesize, int)
|
||||
|
||||
@@ -39,18 +39,6 @@ pub enum FormatConversion {
|
||||
Ascii = b'b',
|
||||
}
|
||||
|
||||
impl FormatParse for FormatConversion {
|
||||
fn parse(text: &Wtf8) -> (Option<Self>, &Wtf8) {
|
||||
let Some(conversion) = Self::from_string(text) else {
|
||||
return (None, text);
|
||||
};
|
||||
let mut chars = text.code_points();
|
||||
chars.next(); // Consume the bang
|
||||
chars.next(); // Consume one r,s,a char
|
||||
(Some(conversion), chars.as_wtf8())
|
||||
}
|
||||
}
|
||||
|
||||
impl FormatConversion {
|
||||
#[must_use]
|
||||
pub fn from_char(c: CodePoint) -> Option<Self> {
|
||||
@@ -61,15 +49,6 @@ impl FormatConversion {
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_string(text: &Wtf8) -> Option<Self> {
|
||||
let mut chars = text.code_points();
|
||||
if chars.next()? != '!' {
|
||||
return None;
|
||||
}
|
||||
|
||||
Self::from_char(chars.next()?)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
@@ -222,7 +201,6 @@ impl FormatParse for FormatType {
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct FormatSpec {
|
||||
conversion: Option<FormatConversion>,
|
||||
fill: Option<CodePoint>,
|
||||
align: Option<FormatAlign>,
|
||||
align_specified: bool,
|
||||
@@ -351,8 +329,6 @@ impl FormatSpec {
|
||||
}
|
||||
|
||||
fn _parse(text: &Wtf8) -> Result<Self, FormatSpecError> {
|
||||
// get_integer in CPython
|
||||
let (conversion, text) = FormatConversion::parse(text);
|
||||
let (mut fill, mut align, text) = parse_fill_and_align(text);
|
||||
let align_specified = align.is_some();
|
||||
let (sign, text) = FormatSign::parse(text);
|
||||
@@ -381,7 +357,6 @@ impl FormatSpec {
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
conversion,
|
||||
fill,
|
||||
align,
|
||||
align_specified,
|
||||
@@ -835,7 +810,6 @@ impl FormatSpec {
|
||||
/// parses to. Written as a destructure so a new field cannot be forgotten here.
|
||||
fn is_empty(&self) -> bool {
|
||||
let Self {
|
||||
conversion,
|
||||
fill,
|
||||
align,
|
||||
align_specified,
|
||||
@@ -848,8 +822,7 @@ impl FormatSpec {
|
||||
frac_grouping_option,
|
||||
format_type,
|
||||
} = self;
|
||||
conversion.is_none()
|
||||
&& fill.is_none()
|
||||
fill.is_none()
|
||||
&& align.is_none()
|
||||
&& !align_specified
|
||||
&& sign.is_none()
|
||||
@@ -1717,7 +1690,6 @@ mod tests {
|
||||
#[test]
|
||||
fn width_only() {
|
||||
let expected = Ok(FormatSpec {
|
||||
conversion: None,
|
||||
fill: None,
|
||||
align: None,
|
||||
align_specified: false,
|
||||
@@ -1736,7 +1708,6 @@ mod tests {
|
||||
#[test]
|
||||
fn fill_and_width() {
|
||||
let expected = Ok(FormatSpec {
|
||||
conversion: None,
|
||||
fill: Some('<'.into()),
|
||||
align: Some(FormatAlign::Right),
|
||||
align_specified: true,
|
||||
@@ -1755,7 +1726,6 @@ mod tests {
|
||||
#[test]
|
||||
fn all() {
|
||||
let expected = Ok(FormatSpec {
|
||||
conversion: None,
|
||||
fill: Some('<'.into()),
|
||||
align: Some(FormatAlign::Right),
|
||||
align_specified: true,
|
||||
|
||||
@@ -19,9 +19,28 @@ pub use libc::{F_FULLFSYNC, F_NOCACHE};
|
||||
#[cfg(target_os = "freebsd")]
|
||||
pub use libc::{F_DUP2FD, F_DUP2FD_CLOEXEC};
|
||||
|
||||
#[cfg(any(target_os = "android", target_os = "linux"))]
|
||||
#[cfg(any(target_os = "android", target_os = "linux", target_vendor = "apple"))]
|
||||
pub use libc::{F_OFD_GETLK, F_OFD_SETLK, F_OFD_SETLKW};
|
||||
|
||||
/// `FASYNC` is what `fcntl.h` calls the flag `O_ASYNC` stands for.
|
||||
#[cfg(not(target_os = "wasi"))]
|
||||
pub const FASYNC: libc::c_int = libc::O_ASYNC;
|
||||
|
||||
#[cfg(target_vendor = "apple")]
|
||||
pub use libc::F_RDAHEAD;
|
||||
|
||||
/// The commands `fcntl.h` carries that libc does not name.
|
||||
#[cfg(target_vendor = "apple")]
|
||||
mod darwin_fcntl {
|
||||
pub const F_SETNOSIGPIPE: libc::c_int = 73;
|
||||
pub const F_GETNOSIGPIPE: libc::c_int = 74;
|
||||
pub const F_SETLEASE: libc::c_int = 106;
|
||||
pub const F_GETLEASE: libc::c_int = 107;
|
||||
}
|
||||
|
||||
#[cfg(target_vendor = "apple")]
|
||||
pub use darwin_fcntl::{F_GETLEASE, F_GETNOSIGPIPE, F_SETLEASE, F_SETNOSIGPIPE};
|
||||
|
||||
#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
|
||||
pub use libc::{
|
||||
F_ADD_SEALS, F_GET_SEALS, F_GETLEASE, F_GETPIPE_SZ, F_NOTIFY, F_SEAL_GROW, F_SEAL_SEAL,
|
||||
|
||||
@@ -7,10 +7,84 @@ use std::io;
|
||||
|
||||
#[cfg(unix)]
|
||||
pub use libc::{
|
||||
MADV_DONTNEED, MADV_NORMAL, MADV_RANDOM, MADV_SEQUENTIAL, MADV_WILLNEED, MAP_ANON,
|
||||
MAP_ANONYMOUS, MAP_PRIVATE, MAP_SHARED, PROT_EXEC, PROT_READ, PROT_WRITE,
|
||||
MADV_DONTNEED, MADV_NORMAL, MADV_RANDOM, MADV_SEQUENTIAL, MADV_WILLNEED, PROT_EXEC, PROT_READ,
|
||||
PROT_WRITE,
|
||||
};
|
||||
|
||||
#[cfg(unix)]
|
||||
bitflags::bitflags! {
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub struct MapFlags: libc::c_int {
|
||||
const MAP_SHARED = libc::MAP_SHARED;
|
||||
const MAP_PRIVATE = libc::MAP_PRIVATE;
|
||||
const MAP_ANON = libc::MAP_ANON;
|
||||
const MAP_ANONYMOUS = libc::MAP_ANONYMOUS;
|
||||
|
||||
#[cfg(all(target_os = "linux", target_arch = "x86_64", target_env = "gnu"))]
|
||||
const MAP_DENYWRITE = libc::MAP_DENYWRITE;
|
||||
#[cfg(all(target_os = "linux", target_arch = "x86_64", target_env = "gnu"))]
|
||||
const MAP_EXECUTABLE = libc::MAP_EXECUTABLE;
|
||||
#[cfg(all(target_os = "linux", target_arch = "x86_64", target_env = "gnu"))]
|
||||
const MAP_POPULATE = libc::MAP_POPULATE;
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "openbsd", target_os = "netbsd"))]
|
||||
const MAP_STACK = libc::MAP_STACK;
|
||||
|
||||
#[cfg(target_vendor = "apple")]
|
||||
const MAP_NORESERVE = libc::MAP_NORESERVE;
|
||||
#[cfg(target_vendor = "apple")]
|
||||
const MAP_NOEXTEND = libc::MAP_NOEXTEND;
|
||||
#[cfg(target_vendor = "apple")]
|
||||
const MAP_HASSEMAPHORE = libc::MAP_HASSEMAPHORE;
|
||||
#[cfg(target_vendor = "apple")]
|
||||
const MAP_NOCACHE = libc::MAP_NOCACHE;
|
||||
#[cfg(target_vendor = "apple")]
|
||||
const MAP_JIT = libc::MAP_JIT;
|
||||
// libc does not name these `sys/mman.h` flags.
|
||||
#[cfg(target_vendor = "apple")]
|
||||
const MAP_RESILIENT_CODESIGN = 0x2000;
|
||||
#[cfg(target_vendor = "apple")]
|
||||
const MAP_RESILIENT_MEDIA = 0x4000;
|
||||
#[cfg(target_vendor = "apple")]
|
||||
const MAP_32BIT = 0x8000;
|
||||
#[cfg(target_vendor = "apple")]
|
||||
const MAP_TRANSLATED_ALLOW_EXECUTE = 0x20000;
|
||||
#[cfg(target_vendor = "apple")]
|
||||
const MAP_UNIX03 = 0x40000;
|
||||
#[cfg(target_vendor = "apple")]
|
||||
const MAP_TPRO = 0x80000;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
macro_rules! map_flag_consts {
|
||||
($($name:ident),+ $(,)?) => {
|
||||
$(pub const $name: libc::c_int = MapFlags::$name.bits();)+
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
map_flag_consts! {
|
||||
MAP_SHARED, MAP_PRIVATE, MAP_ANON, MAP_ANONYMOUS,
|
||||
}
|
||||
|
||||
#[cfg(all(target_os = "linux", target_arch = "x86_64", target_env = "gnu"))]
|
||||
map_flag_consts! {
|
||||
MAP_DENYWRITE, MAP_EXECUTABLE, MAP_POPULATE,
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "openbsd", target_os = "netbsd"))]
|
||||
map_flag_consts! {
|
||||
MAP_STACK,
|
||||
}
|
||||
|
||||
#[cfg(target_vendor = "apple")]
|
||||
map_flag_consts! {
|
||||
MAP_HASSEMAPHORE, MAP_JIT, MAP_NOCACHE, MAP_NOEXTEND, MAP_NORESERVE,
|
||||
MAP_RESILIENT_CODESIGN, MAP_RESILIENT_MEDIA, MAP_32BIT,
|
||||
MAP_TRANSLATED_ALLOW_EXECUTE, MAP_UNIX03, MAP_TPRO,
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub use libc::{MADV_FREE_REUSABLE, MADV_FREE_REUSE};
|
||||
|
||||
@@ -50,12 +124,6 @@ pub use libc::{
|
||||
))]
|
||||
pub use libc::MADV_SOFT_OFFLINE;
|
||||
|
||||
#[cfg(all(target_os = "linux", target_arch = "x86_64", target_env = "gnu"))]
|
||||
pub use libc::{MAP_DENYWRITE, MAP_EXECUTABLE, MAP_POPULATE};
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "openbsd", target_os = "netbsd"))]
|
||||
pub use libc::MAP_STACK;
|
||||
|
||||
#[cfg(target_os = "freebsd")]
|
||||
pub use libc::{MADV_AUTOSYNC, MADV_CORE, MADV_NOCORE, MADV_NOSYNC, MADV_PROTECT};
|
||||
|
||||
|
||||
@@ -948,6 +948,20 @@ pub fn kill(pid: i32, sig: i32) -> std::io::Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
/// `killpg(2)`: signal every process in a process group.
|
||||
///
|
||||
/// Not `kill(-pgid, sig)`: that spelling reads a negative pid, and `kill`
|
||||
/// takes its argument from Python where a caller can already pass one, so the
|
||||
/// two are not interchangeable at this layer.
|
||||
pub fn killpg(pgid: i32, sig: i32) -> std::io::Result<()> {
|
||||
let ret = unsafe { libc::killpg(pgid, sig) };
|
||||
if ret == -1 {
|
||||
Err(std::io::Error::last_os_error())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "wasi", target_os = "android")))]
|
||||
pub fn setuid(uid: u32) -> std::io::Result<()> {
|
||||
nix::unistd::setuid(uid_from_raw(uid)).map_err(std::io::Error::from)
|
||||
|
||||
@@ -2,7 +2,10 @@ use core::mem::MaybeUninit;
|
||||
use std::io;
|
||||
|
||||
#[cfg(unix)]
|
||||
pub use libc::{EINTR, FD_SETSIZE, POLLERR, POLLHUP, POLLIN, POLLNVAL, POLLOUT, POLLPRI};
|
||||
pub use libc::{
|
||||
EINTR, FD_SETSIZE, PIPE_BUF, POLLERR, POLLHUP, POLLIN, POLLNVAL, POLLOUT, POLLPRI, POLLRDBAND,
|
||||
POLLRDNORM, POLLWRBAND, POLLWRNORM,
|
||||
};
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "android", target_os = "redox"))]
|
||||
pub use libc::{
|
||||
|
||||
@@ -13,6 +13,9 @@ pub use libc::{
|
||||
#[cfg(not(target_os = "redox"))]
|
||||
pub use libc::{LOG_AUTHPRIV, LOG_CRON, LOG_PERROR};
|
||||
|
||||
#[cfg(target_vendor = "apple")]
|
||||
pub use libc::{LOG_FTP, LOG_INSTALL, LOG_LAUNCHD, LOG_NETINFO, LOG_RAS, LOG_REMOTEAUTH};
|
||||
|
||||
#[derive(Debug)]
|
||||
enum GlobalIdent {
|
||||
Explicit(Box<CStr>),
|
||||
|
||||
@@ -50,6 +50,12 @@ pub use libc::{
|
||||
TIOCPKT_NOSTOP, TIOCPKT_START, TIOCPKT_STOP,
|
||||
};
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub use libc::{
|
||||
_POSIX_VDISABLE, ALTWERASE, B7200, B14400, B28800, B76800, CIGNORE, EXTPROC, IUTF8, MDMBUF,
|
||||
NOKERNINFO, ONOEOT, OXTABS, VDSUSP, VSTATUS,
|
||||
};
|
||||
|
||||
#[cfg(any(
|
||||
target_os = "android",
|
||||
target_os = "freebsd",
|
||||
|
||||
+25
-97
@@ -5,9 +5,7 @@ pub(crate) use _heapq::module_def;
|
||||
mod _heapq {
|
||||
|
||||
use crate::vm::{
|
||||
AsObject, PyObjectRef, PyResult, VirtualMachine,
|
||||
builtins::{PyList, PyListRef},
|
||||
types::PyComparisonOp,
|
||||
PyObjectRef, PyResult, VirtualMachine, builtins::PyListRef, types::PyComparisonOp,
|
||||
};
|
||||
|
||||
/// [CPython's siftdown](https://github.com/python/cpython/blob/v3.14.5/Modules/_heapqmodule.c#L25-L68)
|
||||
@@ -125,15 +123,8 @@ mod _heapq {
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn heappush(heap: PyObjectRef, item: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
|
||||
let lst = heap.downcast::<PyList>().map_err(|obj| {
|
||||
vm.new_type_error(format!(
|
||||
"heappush() argument 1 must be list, not {}",
|
||||
obj.class().name()
|
||||
))
|
||||
})?;
|
||||
|
||||
heappush_internal(&lst, item, siftdown, vm)
|
||||
fn heappush(heap: PyListRef, item: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
|
||||
heappush_internal(&heap, item, siftdown, vm)
|
||||
}
|
||||
|
||||
/// [CPython's heappop_internal](https://github.com/python/cpython/blob/v3.14.5/Modules/_heapqmodule.c#L152-L183)
|
||||
@@ -165,15 +156,8 @@ mod _heapq {
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn heappop(heap: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyObjectRef> {
|
||||
let lst = heap.downcast::<PyList>().map_err(|obj| {
|
||||
vm.new_type_error(format!(
|
||||
"heappop() argument 1 must be list, not {}",
|
||||
obj.class().name()
|
||||
))
|
||||
})?;
|
||||
|
||||
heappop_internal(&lst, siftup, vm)
|
||||
fn heappop(heap: PyListRef, vm: &VirtualMachine) -> PyResult<PyObjectRef> {
|
||||
heappop_internal(&heap, siftup, vm)
|
||||
}
|
||||
|
||||
/// [CPython's heapreplace_internal](https://github.com/python/cpython/blob/v3.14.5/Modules/_heapqmodule.c#L202-L220)
|
||||
@@ -203,35 +187,21 @@ mod _heapq {
|
||||
|
||||
#[pyfunction]
|
||||
fn heapreplace(
|
||||
heap: PyObjectRef,
|
||||
heap: PyListRef,
|
||||
item: PyObjectRef,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<PyObjectRef> {
|
||||
let lst = heap.downcast::<PyList>().map_err(|obj| {
|
||||
vm.new_type_error(format!(
|
||||
"heapreplace() argument 1 must be list, not {}",
|
||||
obj.class().name()
|
||||
))
|
||||
})?;
|
||||
|
||||
heapreplace_internal(&lst, item, siftup, vm)
|
||||
heapreplace_internal(&heap, item, siftup, vm)
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn heappushpop(
|
||||
heap: PyObjectRef,
|
||||
heap: PyListRef,
|
||||
item: PyObjectRef,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<PyObjectRef> {
|
||||
let lst = heap.downcast::<PyList>().map_err(|obj| {
|
||||
vm.new_type_error(format!(
|
||||
"heappushpop() argument 1 must be list, not {}",
|
||||
obj.class().name()
|
||||
))
|
||||
})?;
|
||||
|
||||
let top = {
|
||||
let vec = lst.borrow_vec();
|
||||
let vec = heap.borrow_vec();
|
||||
match vec.first() {
|
||||
Some(v) => v.clone(),
|
||||
None => return Ok(item),
|
||||
@@ -244,7 +214,7 @@ mod _heapq {
|
||||
}
|
||||
|
||||
let returnitem = {
|
||||
let mut vec = lst.borrow_vec_mut();
|
||||
let mut vec = heap.borrow_vec_mut();
|
||||
let root = match vec.first() {
|
||||
Some(v) => v.clone(),
|
||||
None => return Err(vm.new_index_error("index out of range")),
|
||||
@@ -254,7 +224,7 @@ mod _heapq {
|
||||
root
|
||||
};
|
||||
|
||||
siftup(&lst, 0, vm)?;
|
||||
siftup(&heap, 0, vm)?;
|
||||
Ok(returnitem)
|
||||
}
|
||||
|
||||
@@ -333,15 +303,8 @@ mod _heapq {
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn heapify(heap: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
|
||||
let lst = heap.downcast::<PyList>().map_err(|obj| {
|
||||
vm.new_type_error(format!(
|
||||
"heapify() argument 1 must be list, not {}",
|
||||
obj.class().name()
|
||||
))
|
||||
})?;
|
||||
|
||||
heapify_internal(&lst, siftup, vm)
|
||||
fn heapify(heap: PyListRef, vm: &VirtualMachine) -> PyResult<()> {
|
||||
heapify_internal(&heap, siftup, vm)
|
||||
}
|
||||
|
||||
/// [CPython's siftdown_max](https://github.com/python/cpython/blob/v3.14.5/Modules/_heapqmodule.c#L407-L449)
|
||||
@@ -435,72 +398,37 @@ mod _heapq {
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn heappush_max(heap: PyObjectRef, item: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
|
||||
let lst = heap.downcast::<PyList>().map_err(|obj| {
|
||||
vm.new_type_error(format!(
|
||||
"heappush_max() argument 1 must be list, not {}",
|
||||
obj.class().name()
|
||||
))
|
||||
})?;
|
||||
|
||||
heappush_internal(&lst, item, siftdown_max, vm)
|
||||
fn heappush_max(heap: PyListRef, item: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
|
||||
heappush_internal(&heap, item, siftdown_max, vm)
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn heappop_max(heap: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyObjectRef> {
|
||||
let lst = heap.downcast::<PyList>().map_err(|obj| {
|
||||
vm.new_type_error(format!(
|
||||
"heappop_max() argument 1 must be list, not {}",
|
||||
obj.class().name()
|
||||
))
|
||||
})?;
|
||||
|
||||
heappop_internal(&lst, siftup_max, vm)
|
||||
fn heappop_max(heap: PyListRef, vm: &VirtualMachine) -> PyResult<PyObjectRef> {
|
||||
heappop_internal(&heap, siftup_max, vm)
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn heapreplace_max(
|
||||
heap: PyObjectRef,
|
||||
heap: PyListRef,
|
||||
item: PyObjectRef,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<PyObjectRef> {
|
||||
let lst = heap.downcast::<PyList>().map_err(|obj| {
|
||||
vm.new_type_error(format!(
|
||||
"heapreplace_max() argument 1 must be list, not {}",
|
||||
obj.class().name()
|
||||
))
|
||||
})?;
|
||||
|
||||
heapreplace_internal(&lst, item, siftup_max, vm)
|
||||
heapreplace_internal(&heap, item, siftup_max, vm)
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn heapify_max(heap: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
|
||||
let lst = heap.downcast::<PyList>().map_err(|obj| {
|
||||
vm.new_type_error(format!(
|
||||
"heapify_max() argument 1 must be list, not {}",
|
||||
obj.class().name()
|
||||
))
|
||||
})?;
|
||||
|
||||
heapify_internal(&lst, siftup_max, vm)
|
||||
fn heapify_max(heap: PyListRef, vm: &VirtualMachine) -> PyResult<()> {
|
||||
heapify_internal(&heap, siftup_max, vm)
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn heappushpop_max(
|
||||
heap: PyObjectRef,
|
||||
heap: PyListRef,
|
||||
item: PyObjectRef,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<PyObjectRef> {
|
||||
let lst = heap.downcast::<PyList>().map_err(|obj| {
|
||||
vm.new_type_error(format!(
|
||||
"heappushpop_max() argument 1 must be list, not {}",
|
||||
obj.class().name()
|
||||
))
|
||||
})?;
|
||||
|
||||
let top = {
|
||||
let vec = lst.borrow_vec();
|
||||
let vec = heap.borrow_vec();
|
||||
match vec.first() {
|
||||
Some(v) => v.clone(),
|
||||
None => return Ok(item),
|
||||
@@ -513,7 +441,7 @@ mod _heapq {
|
||||
}
|
||||
|
||||
let returnitem = {
|
||||
let mut vec = lst.borrow_vec_mut();
|
||||
let mut vec = heap.borrow_vec_mut();
|
||||
let root = match vec.first() {
|
||||
Some(v) => v.clone(),
|
||||
None => return Err(vm.new_index_error("index out of range")),
|
||||
@@ -523,7 +451,7 @@ mod _heapq {
|
||||
root
|
||||
};
|
||||
|
||||
siftup_max(&lst, 0, vm)?;
|
||||
siftup_max(&heap, 0, vm)?;
|
||||
Ok(returnitem)
|
||||
}
|
||||
}
|
||||
@@ -45,10 +45,18 @@ mod fcntl {
|
||||
#[pyattr]
|
||||
use host_fcntl::{F_DUP2FD, F_DUP2FD_CLOEXEC};
|
||||
|
||||
#[cfg(any(target_os = "android", target_os = "linux"))]
|
||||
#[cfg(any(target_os = "android", target_os = "linux", target_vendor = "apple"))]
|
||||
#[pyattr]
|
||||
use host_fcntl::{F_OFD_GETLK, F_OFD_SETLK, F_OFD_SETLKW};
|
||||
|
||||
#[cfg(not(target_os = "wasi"))]
|
||||
#[pyattr]
|
||||
use host_fcntl::FASYNC;
|
||||
|
||||
#[cfg(target_vendor = "apple")]
|
||||
#[pyattr]
|
||||
use host_fcntl::{F_GETLEASE, F_GETNOSIGPIPE, F_RDAHEAD, F_SETLEASE, F_SETNOSIGPIPE};
|
||||
|
||||
#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
|
||||
#[pyattr]
|
||||
use host_fcntl::{
|
||||
|
||||
@@ -122,6 +122,15 @@ mod mmap {
|
||||
#[pyattr]
|
||||
use host_mmap::{MADV_AUTOSYNC, MADV_CORE, MADV_NOCORE, MADV_NOSYNC, MADV_PROTECT};
|
||||
|
||||
// Darwin-specific mapping flags
|
||||
#[cfg(target_vendor = "apple")]
|
||||
#[pyattr]
|
||||
use host_mmap::{
|
||||
MAP_32BIT, MAP_HASSEMAPHORE, MAP_JIT, MAP_NOCACHE, MAP_NOEXTEND, MAP_NORESERVE,
|
||||
MAP_RESILIENT_CODESIGN, MAP_RESILIENT_MEDIA, MAP_TPRO, MAP_TRANSLATED_ALLOW_EXECUTE,
|
||||
MAP_UNIX03,
|
||||
};
|
||||
|
||||
#[pyattr]
|
||||
const ACCESS_DEFAULT: u32 = AccessMode::Default as u32;
|
||||
#[pyattr]
|
||||
|
||||
@@ -117,6 +117,18 @@ mod resource {
|
||||
}
|
||||
}
|
||||
|
||||
/// The exception the module reports its failures through, which is the one
|
||||
/// every other call raises.
|
||||
#[pyattr(name = "error", once)]
|
||||
fn error(vm: &VirtualMachine) -> PyObjectRef {
|
||||
vm.ctx.exceptions.os_error.to_owned().into()
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn getpagesize() -> usize {
|
||||
rustpython_host_env::os::page_size()
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn getrusage(who: i32, vm: &VirtualMachine) -> PyResult<RUsageData> {
|
||||
let res = host_resource::getrusage(who);
|
||||
|
||||
@@ -180,7 +180,10 @@ mod decl {
|
||||
|
||||
#[cfg(unix)]
|
||||
#[pyattr]
|
||||
use host_select::{POLLERR, POLLHUP, POLLIN, POLLNVAL, POLLOUT, POLLPRI};
|
||||
use host_select::{
|
||||
PIPE_BUF, POLLERR, POLLHUP, POLLIN, POLLNVAL, POLLOUT, POLLPRI, POLLRDBAND, POLLRDNORM,
|
||||
POLLWRBAND, POLLWRNORM,
|
||||
};
|
||||
|
||||
#[cfg(unix)]
|
||||
pub(super) mod poll {
|
||||
|
||||
@@ -126,6 +126,14 @@ mod _socket {
|
||||
#[pyattr]
|
||||
use c::SO_SETFIB;
|
||||
|
||||
#[cfg(target_vendor = "apple")]
|
||||
#[pyattr]
|
||||
use c::{
|
||||
IP_ADD_SOURCE_MEMBERSHIP, IP_BLOCK_SOURCE, IP_DROP_SOURCE_MEMBERSHIP, IP_PKTINFO,
|
||||
IP_RECVTTL, IP_UNBLOCK_SOURCE, IPPROTO_MAX, IPPROTO_SCTP, MSG_NOSIGNAL,
|
||||
TCP_CONNECTION_INFO,
|
||||
};
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
#[pyattr]
|
||||
use c::{
|
||||
|
||||
@@ -24,6 +24,10 @@ mod syslog {
|
||||
#[pyattr]
|
||||
use host_syslog::{LOG_AUTHPRIV, LOG_CRON, LOG_PERROR};
|
||||
|
||||
#[cfg(target_vendor = "apple")]
|
||||
#[pyattr]
|
||||
use host_syslog::{LOG_FTP, LOG_INSTALL, LOG_LAUNCHD, LOG_NETINFO, LOG_RAS, LOG_REMOTEAUTH};
|
||||
|
||||
fn get_argv(vm: &VirtualMachine) -> Option<PyStrRef> {
|
||||
if let Some(argv) = vm.state.config.settings.argv.first()
|
||||
&& !argv.is_empty()
|
||||
|
||||
@@ -52,6 +52,12 @@ mod termios {
|
||||
))]
|
||||
#[pyattr]
|
||||
use host_termios::TCSASOFT;
|
||||
#[cfg(target_os = "macos")]
|
||||
#[pyattr]
|
||||
use host_termios::{
|
||||
_POSIX_VDISABLE, ALTWERASE, B7200, B14400, B28800, B76800, CIGNORE, EXTPROC, IUTF8, MDMBUF,
|
||||
NOKERNINFO, ONOEOT, OXTABS, VDSUSP, VSTATUS,
|
||||
};
|
||||
#[pyattr]
|
||||
use host_termios::{
|
||||
B0, B50, B75, B110, B134, B150, B200, B300, B600, B1200, B1800, B2400, B4800, B9600,
|
||||
|
||||
@@ -268,59 +268,28 @@ impl PyFunction {
|
||||
|
||||
let mut vararg_offset = total_args;
|
||||
// Pack other positional arguments in to *args:
|
||||
if code.flags.contains(bytecode::CodeFlags::VARARGS) {
|
||||
let too_many_positional = if code.flags.contains(bytecode::CodeFlags::VARARGS) {
|
||||
let vararg_value = vm.ctx.new_tuple(args_iter.collect());
|
||||
fastlocals[vararg_offset] = Some(vararg_value.into());
|
||||
vararg_offset += 1;
|
||||
false
|
||||
} else {
|
||||
// Check the number of positional arguments
|
||||
if nargs > n_expected_args {
|
||||
let n_defaults = self
|
||||
.defaults_and_kwdefaults
|
||||
.lock()
|
||||
.0
|
||||
.as_ref()
|
||||
.map_or(0, |d| d.len());
|
||||
let n_required = n_expected_args - n_defaults;
|
||||
let takes_msg = if n_defaults > 0 {
|
||||
format!("from {n_required} to {n_expected_args}")
|
||||
} else {
|
||||
n_expected_args.to_string()
|
||||
};
|
||||
nargs > n_expected_args
|
||||
};
|
||||
|
||||
// Count keyword-only arguments that were actually provided
|
||||
let kw_only_given = if code.kwonlyarg_count > 0 {
|
||||
let start = code.arg_count as usize;
|
||||
let end = start + code.kwonlyarg_count as usize;
|
||||
code.varnames[start..end]
|
||||
.iter()
|
||||
.filter(|name| func_args.kwargs.contains_key(name.as_str()))
|
||||
.count()
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
let given_msg = if kw_only_given > 0 {
|
||||
format!(
|
||||
"{} positional argument{} (and {} keyword-only argument{}) were",
|
||||
nargs,
|
||||
if nargs == 1 { "" } else { "s" },
|
||||
kw_only_given,
|
||||
if kw_only_given == 1 { "" } else { "s" },
|
||||
)
|
||||
} else {
|
||||
format!("{} {}", nargs, if nargs == 1 { "was" } else { "were" })
|
||||
};
|
||||
|
||||
return Err(vm.new_type_error(format!(
|
||||
"{}() takes {} positional argument{} but {} given",
|
||||
self.__qualname__(),
|
||||
takes_msg,
|
||||
if n_expected_args == 1 { "" } else { "s" },
|
||||
given_msg,
|
||||
)));
|
||||
}
|
||||
}
|
||||
// The keyword-only parameters the call brought are counted alongside
|
||||
// the positional ones it brought too many of, before the keywords
|
||||
// themselves are taken out of the call.
|
||||
let kw_only_given = if too_many_positional && code.kwonlyarg_count > 0 {
|
||||
let start = code.arg_count as usize;
|
||||
let end = start + code.kwonlyarg_count as usize;
|
||||
code.varnames[start..end]
|
||||
.iter()
|
||||
.filter(|name| func_args.kwargs.contains_key(name.as_str()))
|
||||
.count()
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
// Do we support `**kwargs` ?
|
||||
let kwargs = if code.flags.contains(bytecode::CodeFlags::VARKEYWORDS) {
|
||||
@@ -341,9 +310,9 @@ impl PyFunction {
|
||||
.map(|(p, _)| p)
|
||||
};
|
||||
|
||||
let mut posonly_passed_as_kwarg = Vec::new();
|
||||
// Handle keyword arguments
|
||||
for (name, value) in func_args.kwargs {
|
||||
let mut kwargs_iter = func_args.kwargs.into_iter();
|
||||
while let Some((name, value)) = kwargs_iter.next() {
|
||||
// Parameter names are plain identifiers, so a non-UTF-8 (surrogate) key
|
||||
// can never match one and just falls through to **kwargs / the error path.
|
||||
let name_str = name.as_str().ok();
|
||||
@@ -362,11 +331,26 @@ impl PyFunction {
|
||||
*slot = Some(value);
|
||||
} else if let Some(kwargs) = kwargs.as_ref() {
|
||||
kwargs.set_item(&name, value, vm)?;
|
||||
} else if name_str
|
||||
.is_some_and(|s| arg_pos(0..code.posonlyarg_count as usize, s).is_some())
|
||||
{
|
||||
posonly_passed_as_kwarg.push(name);
|
||||
} else {
|
||||
// A name the call cannot place is faulted over the whole of
|
||||
// what it named that only position can give, whether that
|
||||
// came before this name or after it.
|
||||
let is_posonly = |name: &Wtf8Buf| {
|
||||
name.as_str()
|
||||
.is_ok_and(|s| arg_pos(0..code.posonlyarg_count as usize, s).is_some())
|
||||
};
|
||||
let mut posonly: Vec<_> = is_posonly(&name)
|
||||
.then(|| name.clone())
|
||||
.into_iter()
|
||||
.collect();
|
||||
posonly.extend(kwargs_iter.map(|(name, _)| name).filter(is_posonly));
|
||||
if !posonly.is_empty() {
|
||||
return Err(vm.new_type_error(format!(
|
||||
"{}() got some positional-only arguments passed as keyword arguments: '{}'",
|
||||
self.__qualname__(),
|
||||
posonly.into_iter().format(", "),
|
||||
)));
|
||||
}
|
||||
return Err(vm.new_type_error(format!(
|
||||
"{}() got an unexpected keyword argument '{}'",
|
||||
self.__qualname__(),
|
||||
@@ -374,11 +358,40 @@ impl PyFunction {
|
||||
)));
|
||||
}
|
||||
}
|
||||
if !posonly_passed_as_kwarg.is_empty() {
|
||||
// The count of positional arguments is faulted once the keywords have
|
||||
// all been placed.
|
||||
if too_many_positional {
|
||||
let n_defaults = self
|
||||
.defaults_and_kwdefaults
|
||||
.lock()
|
||||
.0
|
||||
.as_ref()
|
||||
.map_or(0, |d| d.len());
|
||||
let n_required = n_expected_args - n_defaults;
|
||||
let (takes_msg, plural) = if n_defaults > 0 {
|
||||
(format!("from {n_required} to {n_expected_args}"), true)
|
||||
} else {
|
||||
(n_expected_args.to_string(), n_expected_args != 1)
|
||||
};
|
||||
|
||||
let given_msg = if kw_only_given > 0 {
|
||||
format!(
|
||||
"{} positional argument{} (and {} keyword-only argument{}) were",
|
||||
nargs,
|
||||
if nargs == 1 { "" } else { "s" },
|
||||
kw_only_given,
|
||||
if kw_only_given == 1 { "" } else { "s" },
|
||||
)
|
||||
} else {
|
||||
format!("{} {}", nargs, if nargs == 1 { "was" } else { "were" })
|
||||
};
|
||||
|
||||
return Err(vm.new_type_error(format!(
|
||||
"{}() got some positional-only arguments passed as keyword arguments: '{}'",
|
||||
"{}() takes {} positional argument{} but {} given",
|
||||
self.__qualname__(),
|
||||
posonly_passed_as_kwarg.into_iter().format(", "),
|
||||
takes_msg,
|
||||
if plural { "s" } else { "" },
|
||||
given_msg,
|
||||
)));
|
||||
}
|
||||
|
||||
|
||||
@@ -510,35 +510,38 @@ pub fn object_get_dict(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyDict
|
||||
}
|
||||
pub(crate) fn object_set_dict(
|
||||
obj: PyObjectRef,
|
||||
value: PySetterValue<PyDictRef>,
|
||||
value: PySetterValue,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<()> {
|
||||
let dict = match value {
|
||||
PySetterValue::Assign(dict) => Some(dict),
|
||||
PySetterValue::Assign(value) => Some(downcast_dict(value, vm)?),
|
||||
PySetterValue::Delete => None,
|
||||
};
|
||||
obj.set_dict(dict)
|
||||
.map_err(|_| vm.new_attribute_error("This object has no __dict__"))
|
||||
}
|
||||
|
||||
/// The dictionary an object holds its attributes in, which is one thing and
|
||||
/// one thing only. = subtype_setdict
|
||||
fn downcast_dict(value: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyDictRef> {
|
||||
value.downcast::<PyDict>().map_err(|value| {
|
||||
vm.new_type_error(format!(
|
||||
"__dict__ must be set to a dictionary, not a '{}'",
|
||||
value.class().name()
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
/// = PyObject_GenericSetDict
|
||||
pub fn object_generic_set_dict(
|
||||
obj: PyObjectRef,
|
||||
value: PySetterValue,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<()> {
|
||||
let dict = match value {
|
||||
PySetterValue::Assign(value) => {
|
||||
let dict = value.downcast::<PyDict>().map_err(|value| {
|
||||
vm.new_type_error(format!(
|
||||
"__dict__ must be set to a dictionary, not a '{}'",
|
||||
value.class().name()
|
||||
))
|
||||
})?;
|
||||
PySetterValue::Assign(dict)
|
||||
}
|
||||
PySetterValue::Delete => return Err(vm.new_type_error("cannot delete __dict__")),
|
||||
};
|
||||
object_set_dict(obj, dict, vm)
|
||||
if matches!(value, PySetterValue::Delete) {
|
||||
return Err(vm.new_type_error("cannot delete __dict__"));
|
||||
}
|
||||
object_set_dict(obj, value, vm)
|
||||
}
|
||||
|
||||
pub(crate) fn init(ctx: &'static Context) {
|
||||
|
||||
@@ -466,7 +466,7 @@ impl Constructor for PyStr {
|
||||
let s = vm
|
||||
.state
|
||||
.codec_registry
|
||||
.decode_text(input, enc_str, errors, vm)?;
|
||||
.decode_text_object(input, enc_str, errors, vm)?;
|
||||
Ok(Self::from(s.as_wtf8().to_owned()))
|
||||
} else {
|
||||
let s = input.str(vm)?;
|
||||
|
||||
@@ -603,11 +603,13 @@ impl PyPayload for PyType {
|
||||
}
|
||||
}
|
||||
|
||||
/// The name a class is built under, which the namespace it is built from may
|
||||
/// carry and which is faulted for not being a string. = type_new_set_attrs
|
||||
fn downcast_qualname(value: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyRef<PyStr>> {
|
||||
match value.downcast::<PyStr>() {
|
||||
Ok(value) => Ok(value),
|
||||
Err(value) => Err(vm.new_type_error(format!(
|
||||
"can only assign string to __qualname__, not '{}'",
|
||||
"type __qualname__ must be a str, not {}",
|
||||
value.class().name()
|
||||
))),
|
||||
}
|
||||
@@ -3160,10 +3162,6 @@ fn subtype_set_dict(obj: PyObjectRef, value: PySetterValue, vm: &VirtualMachine)
|
||||
}
|
||||
} else {
|
||||
// _PyObject_SetDict
|
||||
let value = match value {
|
||||
PySetterValue::Assign(obj) => PySetterValue::Assign(obj.try_into_value(vm)?),
|
||||
PySetterValue::Delete => PySetterValue::Delete,
|
||||
};
|
||||
object::object_set_dict(obj, value, vm)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1224,7 +1224,7 @@ pub(crate) fn bytes_decode(
|
||||
};
|
||||
vm.state
|
||||
.codec_registry
|
||||
.decode_text(zelf, encoding, errors, vm)
|
||||
.decode_text_object(zelf, encoding, errors, vm)
|
||||
}
|
||||
|
||||
#[derive(FromArgs)]
|
||||
|
||||
+25
-2
@@ -18,8 +18,8 @@ use crate::{
|
||||
AsObject, Context, Py, PyObject, PyObjectRef, PyResult, TryFromBorrowedObject, TryFromObject,
|
||||
VirtualMachine,
|
||||
builtins::{
|
||||
PyBaseExceptionRef, PyBytes, PyBytesRef, PyStr, PyStrRef, PyTuple, PyTupleRef, PyUtf8Str,
|
||||
PyUtf8StrRef,
|
||||
PyBaseExceptionRef, PyByteArray, PyBytes, PyBytesRef, PyStr, PyStrRef, PyTuple, PyTupleRef,
|
||||
PyUtf8Str, PyUtf8StrRef,
|
||||
},
|
||||
convert::ToPyObject,
|
||||
function::{ArgBytesLike, PyMethodDef},
|
||||
@@ -342,6 +342,29 @@ impl CodecsRegistry {
|
||||
})
|
||||
}
|
||||
|
||||
/// The text an encoded object holds. An object holding nothing decodes to
|
||||
/// the empty string without the encoding ever being looked up.
|
||||
/// = PyUnicode_FromEncodedObject
|
||||
pub fn decode_text_object(
|
||||
&self,
|
||||
obj: PyObjectRef,
|
||||
encoding: &str,
|
||||
errors: Option<PyUtf8StrRef>,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<PyStrRef> {
|
||||
let empty = if let Some(bytes) = obj.downcast_ref::<PyBytes>() {
|
||||
bytes.as_bytes().is_empty()
|
||||
} else if let Some(bytes) = obj.downcast_ref::<PyByteArray>() {
|
||||
bytes.borrow_buf().is_empty()
|
||||
} else {
|
||||
false
|
||||
};
|
||||
if empty {
|
||||
return Ok(vm.ctx.empty_str.to_owned());
|
||||
}
|
||||
self.decode_text(obj, encoding, errors, vm)
|
||||
}
|
||||
|
||||
pub fn decode_text(
|
||||
&self,
|
||||
obj: PyObjectRef,
|
||||
|
||||
@@ -1285,6 +1285,9 @@ pub(crate) fn errno_to_exc_type(errno: i32, vm: &VirtualMachine) -> Option<&'sta
|
||||
errors::EINTR => Some(excs.interrupted_error),
|
||||
errors::EACCES => Some(excs.permission_error),
|
||||
errors::EPERM => Some(excs.permission_error),
|
||||
// A process without the capability to reach a resource.
|
||||
#[cfg(target_vendor = "apple")]
|
||||
errors::ENOTCAPABLE => Some(excs.permission_error),
|
||||
errors::ESRCH => Some(excs.process_lookup_error),
|
||||
errors::ETIMEDOUT => Some(excs.timeout_error),
|
||||
_ => None,
|
||||
@@ -2222,8 +2225,10 @@ pub(super) mod types {
|
||||
{
|
||||
exc.written.store(n);
|
||||
set_filename = false;
|
||||
// Clear filename that was set in py_new
|
||||
// The count leaves neither filename taken, so both are
|
||||
// put back the way `py_new` found them.
|
||||
let _ = unsafe { exc.filename.swap(None) };
|
||||
let _ = unsafe { exc.filename2.swap(None) };
|
||||
}
|
||||
if set_filename {
|
||||
let _ = unsafe { exc.filename.swap(Some(third_arg.clone())) };
|
||||
@@ -2247,19 +2252,21 @@ pub(super) mod types {
|
||||
new_args.args[0] = errno_obj;
|
||||
}
|
||||
}
|
||||
if len == 5 {
|
||||
let _ = unsafe { exc.filename2.swap(new_args.args.get(4).cloned()) };
|
||||
}
|
||||
}
|
||||
|
||||
// args are truncated to 2 for compatibility (only when 2-5 args and filename is not None)
|
||||
// truncation happens inside "if (filename && filename != Py_None)" block
|
||||
// A second filename, and the two arguments the rest are cut back
|
||||
// to, both follow the filename itself having been taken.
|
||||
let has_filename = exc
|
||||
.filename
|
||||
.to_owned()
|
||||
.as_ref()
|
||||
.is_some_and(|f| !vm.is_none(f));
|
||||
if (3..=5).contains(&len) && has_filename {
|
||||
if let Some(filename2) = new_args.args.get(4)
|
||||
&& !vm.is_none(filename2)
|
||||
{
|
||||
let _ = unsafe { exc.filename2.swap(Some(filename2.clone())) };
|
||||
}
|
||||
new_args.args.truncate(2);
|
||||
}
|
||||
PyBaseException::slot_init(zelf, new_args, vm)
|
||||
@@ -2708,15 +2715,20 @@ pub(super) mod types {
|
||||
let location_tup_len = location_tuple.len();
|
||||
|
||||
match location_tup_len {
|
||||
4 | 6 => {}
|
||||
4 | 6 | 7 => {}
|
||||
5 => {
|
||||
return Err(vm.new_type_error(
|
||||
"end_offset must be provided when end_lineno is provided",
|
||||
));
|
||||
}
|
||||
_ => {
|
||||
given if given < 4 => {
|
||||
return Err(vm.new_type_error(format!(
|
||||
"function takes exactly 4 or 6 arguments ({location_tup_len} given)"
|
||||
"function takes at least 4 arguments ({given} given)"
|
||||
)));
|
||||
}
|
||||
given => {
|
||||
return Err(vm.new_type_error(format!(
|
||||
"function takes at most 7 arguments ({given} given)"
|
||||
)));
|
||||
}
|
||||
}
|
||||
@@ -2784,6 +2796,18 @@ pub(super) mod types {
|
||||
#[repr(transparent)]
|
||||
pub struct PyValueError(PyException);
|
||||
|
||||
/// Check the fixed arity expected by the tuple parser before converting
|
||||
/// any of the values it was given.
|
||||
fn parse_tuple_arity(args: &FuncArgs, count: usize, vm: &VirtualMachine) -> PyResult<()> {
|
||||
let given = args.args.len();
|
||||
if given == count {
|
||||
return Ok(());
|
||||
}
|
||||
Err(vm.new_type_error(format!(
|
||||
"function takes exactly {count} arguments ({given} given)"
|
||||
)))
|
||||
}
|
||||
|
||||
#[pyexception(name, base = PyValueError, ctx = "unicode_error", impl)]
|
||||
#[derive(Debug)]
|
||||
#[repr(transparent)]
|
||||
@@ -2830,6 +2854,7 @@ pub(super) mod types {
|
||||
type Args = FuncArgs;
|
||||
|
||||
fn slot_init(zelf: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> {
|
||||
parse_tuple_arity(&args, 5, vm)?;
|
||||
type Args = (PyStrRef, ArgBytesLike, isize, isize, PyStrRef);
|
||||
let (encoding, object, start, end, reason): Args = args.bind(vm)?;
|
||||
set_attrs!(zelf, vm,
|
||||
@@ -2889,6 +2914,7 @@ pub(super) mod types {
|
||||
type Args = FuncArgs;
|
||||
|
||||
fn slot_init(zelf: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> {
|
||||
parse_tuple_arity(&args, 5, vm)?;
|
||||
type Args = (PyStrRef, PyStrRef, isize, isize, PyStrRef);
|
||||
let (encoding, object, start, end, reason): Args = args.bind(vm)?;
|
||||
set_attrs!(zelf, vm,
|
||||
@@ -2946,6 +2972,7 @@ pub(super) mod types {
|
||||
type Args = FuncArgs;
|
||||
|
||||
fn slot_init(zelf: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> {
|
||||
parse_tuple_arity(&args, 4, vm)?;
|
||||
type Args = (PyStrRef, isize, isize, PyStrRef);
|
||||
let (object, start, end, reason): Args = args.bind(vm)?;
|
||||
set_attrs!(zelf, vm,
|
||||
|
||||
@@ -16,7 +16,7 @@ mod _codecs {
|
||||
builtins::{PyStrRef, PyUtf8StrRef},
|
||||
codecs,
|
||||
exceptions::nul_char_error,
|
||||
function::{ArgBytesLike, FuncArgs},
|
||||
function::{ArgBytesLike, PosArgs},
|
||||
};
|
||||
|
||||
#[pyfunction]
|
||||
@@ -256,107 +256,107 @@ mod _codecs {
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn readbuffer_encode(args: FuncArgs, vm: &VirtualMachine) -> PyResult {
|
||||
fn readbuffer_encode(args: PosArgs, vm: &VirtualMachine) -> PyResult {
|
||||
delegate_pycodecs!(readbuffer_encode, args, vm)
|
||||
}
|
||||
#[pyfunction]
|
||||
fn escape_encode(args: FuncArgs, vm: &VirtualMachine) -> PyResult {
|
||||
fn escape_encode(args: PosArgs, vm: &VirtualMachine) -> PyResult {
|
||||
delegate_pycodecs!(escape_encode, args, vm)
|
||||
}
|
||||
#[pyfunction]
|
||||
fn escape_decode(args: FuncArgs, vm: &VirtualMachine) -> PyResult {
|
||||
fn escape_decode(args: PosArgs, vm: &VirtualMachine) -> PyResult {
|
||||
delegate_pycodecs!(escape_decode, args, vm)
|
||||
}
|
||||
#[pyfunction]
|
||||
fn unicode_escape_encode(args: FuncArgs, vm: &VirtualMachine) -> PyResult {
|
||||
fn unicode_escape_encode(args: PosArgs, vm: &VirtualMachine) -> PyResult {
|
||||
delegate_pycodecs!(unicode_escape_encode, args, vm)
|
||||
}
|
||||
#[pyfunction]
|
||||
fn unicode_escape_decode(args: FuncArgs, vm: &VirtualMachine) -> PyResult {
|
||||
fn unicode_escape_decode(args: PosArgs, vm: &VirtualMachine) -> PyResult {
|
||||
delegate_pycodecs!(unicode_escape_decode, args, vm)
|
||||
}
|
||||
#[pyfunction]
|
||||
fn raw_unicode_escape_encode(args: FuncArgs, vm: &VirtualMachine) -> PyResult {
|
||||
fn raw_unicode_escape_encode(args: PosArgs, vm: &VirtualMachine) -> PyResult {
|
||||
delegate_pycodecs!(raw_unicode_escape_encode, args, vm)
|
||||
}
|
||||
#[pyfunction]
|
||||
fn raw_unicode_escape_decode(args: FuncArgs, vm: &VirtualMachine) -> PyResult {
|
||||
fn raw_unicode_escape_decode(args: PosArgs, vm: &VirtualMachine) -> PyResult {
|
||||
delegate_pycodecs!(raw_unicode_escape_decode, args, vm)
|
||||
}
|
||||
#[pyfunction]
|
||||
fn utf_7_encode(args: FuncArgs, vm: &VirtualMachine) -> PyResult {
|
||||
fn utf_7_encode(args: PosArgs, vm: &VirtualMachine) -> PyResult {
|
||||
delegate_pycodecs!(utf_7_encode, args, vm)
|
||||
}
|
||||
#[pyfunction]
|
||||
fn utf_7_decode(args: FuncArgs, vm: &VirtualMachine) -> PyResult {
|
||||
fn utf_7_decode(args: PosArgs, vm: &VirtualMachine) -> PyResult {
|
||||
delegate_pycodecs!(utf_7_decode, args, vm)
|
||||
}
|
||||
#[pyfunction]
|
||||
fn utf_16_encode(args: FuncArgs, vm: &VirtualMachine) -> PyResult {
|
||||
fn utf_16_encode(args: PosArgs, vm: &VirtualMachine) -> PyResult {
|
||||
delegate_pycodecs!(utf_16_encode, args, vm)
|
||||
}
|
||||
#[pyfunction]
|
||||
fn utf_16_decode(args: FuncArgs, vm: &VirtualMachine) -> PyResult {
|
||||
fn utf_16_decode(args: PosArgs, vm: &VirtualMachine) -> PyResult {
|
||||
delegate_pycodecs!(utf_16_decode, args, vm)
|
||||
}
|
||||
#[pyfunction]
|
||||
fn charmap_encode(args: FuncArgs, vm: &VirtualMachine) -> PyResult {
|
||||
fn charmap_encode(args: PosArgs, vm: &VirtualMachine) -> PyResult {
|
||||
delegate_pycodecs!(charmap_encode, args, vm)
|
||||
}
|
||||
#[pyfunction]
|
||||
fn charmap_decode(args: FuncArgs, vm: &VirtualMachine) -> PyResult {
|
||||
fn charmap_decode(args: PosArgs, vm: &VirtualMachine) -> PyResult {
|
||||
delegate_pycodecs!(charmap_decode, args, vm)
|
||||
}
|
||||
#[pyfunction]
|
||||
fn charmap_build(args: FuncArgs, vm: &VirtualMachine) -> PyResult {
|
||||
fn charmap_build(args: PosArgs, vm: &VirtualMachine) -> PyResult {
|
||||
delegate_pycodecs!(charmap_build, args, vm)
|
||||
}
|
||||
#[pyfunction]
|
||||
fn utf_16_le_encode(args: FuncArgs, vm: &VirtualMachine) -> PyResult {
|
||||
fn utf_16_le_encode(args: PosArgs, vm: &VirtualMachine) -> PyResult {
|
||||
delegate_pycodecs!(utf_16_le_encode, args, vm)
|
||||
}
|
||||
#[pyfunction]
|
||||
fn utf_16_le_decode(args: FuncArgs, vm: &VirtualMachine) -> PyResult {
|
||||
fn utf_16_le_decode(args: PosArgs, vm: &VirtualMachine) -> PyResult {
|
||||
delegate_pycodecs!(utf_16_le_decode, args, vm)
|
||||
}
|
||||
#[pyfunction]
|
||||
fn utf_16_be_encode(args: FuncArgs, vm: &VirtualMachine) -> PyResult {
|
||||
fn utf_16_be_encode(args: PosArgs, vm: &VirtualMachine) -> PyResult {
|
||||
delegate_pycodecs!(utf_16_be_encode, args, vm)
|
||||
}
|
||||
#[pyfunction]
|
||||
fn utf_16_be_decode(args: FuncArgs, vm: &VirtualMachine) -> PyResult {
|
||||
fn utf_16_be_decode(args: PosArgs, vm: &VirtualMachine) -> PyResult {
|
||||
delegate_pycodecs!(utf_16_be_decode, args, vm)
|
||||
}
|
||||
#[pyfunction]
|
||||
fn utf_16_ex_decode(args: FuncArgs, vm: &VirtualMachine) -> PyResult {
|
||||
fn utf_16_ex_decode(args: PosArgs, vm: &VirtualMachine) -> PyResult {
|
||||
delegate_pycodecs!(utf_16_ex_decode, args, vm)
|
||||
}
|
||||
#[pyfunction]
|
||||
fn utf_32_ex_decode(args: FuncArgs, vm: &VirtualMachine) -> PyResult {
|
||||
fn utf_32_ex_decode(args: PosArgs, vm: &VirtualMachine) -> PyResult {
|
||||
delegate_pycodecs!(utf_32_ex_decode, args, vm)
|
||||
}
|
||||
#[pyfunction]
|
||||
fn utf_32_encode(args: FuncArgs, vm: &VirtualMachine) -> PyResult {
|
||||
fn utf_32_encode(args: PosArgs, vm: &VirtualMachine) -> PyResult {
|
||||
delegate_pycodecs!(utf_32_encode, args, vm)
|
||||
}
|
||||
#[pyfunction]
|
||||
fn utf_32_decode(args: FuncArgs, vm: &VirtualMachine) -> PyResult {
|
||||
fn utf_32_decode(args: PosArgs, vm: &VirtualMachine) -> PyResult {
|
||||
delegate_pycodecs!(utf_32_decode, args, vm)
|
||||
}
|
||||
#[pyfunction]
|
||||
fn utf_32_le_encode(args: FuncArgs, vm: &VirtualMachine) -> PyResult {
|
||||
fn utf_32_le_encode(args: PosArgs, vm: &VirtualMachine) -> PyResult {
|
||||
delegate_pycodecs!(utf_32_le_encode, args, vm)
|
||||
}
|
||||
#[pyfunction]
|
||||
fn utf_32_le_decode(args: FuncArgs, vm: &VirtualMachine) -> PyResult {
|
||||
fn utf_32_le_decode(args: PosArgs, vm: &VirtualMachine) -> PyResult {
|
||||
delegate_pycodecs!(utf_32_le_decode, args, vm)
|
||||
}
|
||||
#[pyfunction]
|
||||
fn utf_32_be_encode(args: FuncArgs, vm: &VirtualMachine) -> PyResult {
|
||||
fn utf_32_be_encode(args: PosArgs, vm: &VirtualMachine) -> PyResult {
|
||||
delegate_pycodecs!(utf_32_be_encode, args, vm)
|
||||
}
|
||||
#[pyfunction]
|
||||
fn utf_32_be_decode(args: FuncArgs, vm: &VirtualMachine) -> PyResult {
|
||||
fn utf_32_be_decode(args: PosArgs, vm: &VirtualMachine) -> PyResult {
|
||||
delegate_pycodecs!(utf_32_be_decode, args, vm)
|
||||
}
|
||||
}
|
||||
@@ -365,14 +365,14 @@ mod _codecs {
|
||||
fn delegate_pycodecs(
|
||||
cell: &'static StaticCell<crate::PyObjectRef>,
|
||||
name: &'static str,
|
||||
args: crate::function::FuncArgs,
|
||||
args: crate::function::PosArgs,
|
||||
vm: &crate::VirtualMachine,
|
||||
) -> crate::PyResult {
|
||||
let f = cell.get_or_try_init(|| {
|
||||
let module = vm.import("_pycodecs", 0)?;
|
||||
module.get_attr(name, vm)
|
||||
})?;
|
||||
f.call(args, vm)
|
||||
f.call(args.into_vec(), vm)
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
|
||||
@@ -5173,10 +5173,16 @@ mod _io {
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(FromArgs)]
|
||||
struct OpenCodeArgs {
|
||||
#[pyarg(any)]
|
||||
path: PyObjectRef,
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn open_code(file: PyObjectRef, vm: &VirtualMachine) -> PyResult {
|
||||
fn open_code(args: OpenCodeArgs, vm: &VirtualMachine) -> PyResult {
|
||||
// TODO: lifecycle hooks or something?
|
||||
io_open(file, Some("rb"), OpenArgs::default(), vm)
|
||||
io_open(args.path, Some("rb"), OpenArgs::default(), vm)
|
||||
}
|
||||
|
||||
#[derive(FromArgs)]
|
||||
|
||||
@@ -7,6 +7,11 @@ mod _stat {
|
||||
#[cfg(windows)]
|
||||
use rustpython_host_env::nt as host_nt;
|
||||
|
||||
use malachite_bigint::Sign;
|
||||
use num_traits::ToPrimitive;
|
||||
|
||||
use crate::{PyObjectRef, PyResult, VirtualMachine, convert::TryFromObject};
|
||||
|
||||
// Use libc::mode_t for Mode to match the system's definition
|
||||
#[cfg(unix)]
|
||||
type Mode = libc::mode_t;
|
||||
@@ -325,9 +330,18 @@ mod _stat {
|
||||
0xffff0000
|
||||
};
|
||||
|
||||
#[pyattr]
|
||||
pub const UF_SETTABLE: u32 = 0x0000ffff;
|
||||
|
||||
#[pyattr]
|
||||
pub const UF_NOUNLINK: u32 = 0x00000010;
|
||||
|
||||
#[pyattr]
|
||||
pub const UF_TRACKED: u32 = 0x00000040;
|
||||
|
||||
#[pyattr]
|
||||
pub const UF_DATAVAULT: u32 = 0x00000080;
|
||||
|
||||
#[pyattr]
|
||||
pub const SF_NOUNLINK: u32 = 0x00100000;
|
||||
|
||||
@@ -382,88 +396,111 @@ mod _stat {
|
||||
#[pyattr]
|
||||
pub const ST_CTIME: u32 = 9;
|
||||
|
||||
/// The mode an argument stands for, which is read as an unsigned long and
|
||||
/// then measured against the range a mode has. = mode_converter
|
||||
#[derive(Copy, Clone)]
|
||||
struct ModeArg(Mode);
|
||||
|
||||
impl TryFromObject for ModeArg {
|
||||
fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
|
||||
let index = obj.try_index(vm)?;
|
||||
let value = index.as_bigint();
|
||||
// = PyLong_AsUnsignedLong
|
||||
if value.sign() == Sign::Minus {
|
||||
return Err(vm.new_overflow_error("can't convert negative value to unsigned int"));
|
||||
}
|
||||
let value = value.to_u64().ok_or_else(|| {
|
||||
vm.new_overflow_error("Python int too large to convert to C unsigned long")
|
||||
})?;
|
||||
Mode::try_from(value)
|
||||
.map(Self)
|
||||
.map_err(|_| vm.new_overflow_error("mode out of range"))
|
||||
}
|
||||
}
|
||||
|
||||
const S_IFMT: Mode = 0o170000;
|
||||
|
||||
const S_IMODE: Mode = 0o7777;
|
||||
|
||||
#[pyfunction]
|
||||
#[allow(non_snake_case)]
|
||||
const fn S_ISDIR(mode: Mode) -> bool {
|
||||
(mode & S_IFMT) == S_IFDIR
|
||||
const fn S_ISDIR(mode: ModeArg) -> bool {
|
||||
(mode.0 & S_IFMT) == S_IFDIR
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
#[allow(non_snake_case)]
|
||||
const fn S_ISCHR(mode: Mode) -> bool {
|
||||
(mode & S_IFMT) == S_IFCHR
|
||||
const fn S_ISCHR(mode: ModeArg) -> bool {
|
||||
(mode.0 & S_IFMT) == S_IFCHR
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
#[allow(non_snake_case)]
|
||||
const fn S_ISREG(mode: Mode) -> bool {
|
||||
(mode & S_IFMT) == S_IFREG
|
||||
const fn S_ISREG(mode: ModeArg) -> bool {
|
||||
(mode.0 & S_IFMT) == S_IFREG
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
#[allow(non_snake_case)]
|
||||
const fn S_ISBLK(mode: Mode) -> bool {
|
||||
(mode & S_IFMT) == S_IFBLK
|
||||
const fn S_ISBLK(mode: ModeArg) -> bool {
|
||||
(mode.0 & S_IFMT) == S_IFBLK
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
#[allow(non_snake_case)]
|
||||
const fn S_ISFIFO(mode: Mode) -> bool {
|
||||
(mode & S_IFMT) == S_IFIFO
|
||||
const fn S_ISFIFO(mode: ModeArg) -> bool {
|
||||
(mode.0 & S_IFMT) == S_IFIFO
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
#[allow(non_snake_case)]
|
||||
const fn S_ISLNK(mode: Mode) -> bool {
|
||||
(mode & S_IFMT) == S_IFLNK
|
||||
const fn S_ISLNK(mode: ModeArg) -> bool {
|
||||
(mode.0 & S_IFMT) == S_IFLNK
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
#[allow(non_snake_case)]
|
||||
const fn S_ISSOCK(mode: Mode) -> bool {
|
||||
(mode & S_IFMT) == S_IFSOCK
|
||||
const fn S_ISSOCK(mode: ModeArg) -> bool {
|
||||
(mode.0 & S_IFMT) == S_IFSOCK
|
||||
}
|
||||
|
||||
// TODO: RUSTPYTHON Support Solaris
|
||||
#[pyfunction]
|
||||
#[allow(non_snake_case)]
|
||||
const fn S_ISDOOR(_mode: Mode) -> bool {
|
||||
const fn S_ISDOOR(_mode: ModeArg) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
// TODO: RUSTPYTHON Support Solaris
|
||||
#[pyfunction]
|
||||
#[allow(non_snake_case)]
|
||||
const fn S_ISPORT(_mode: Mode) -> bool {
|
||||
const fn S_ISPORT(_mode: ModeArg) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
// TODO: RUSTPYTHON Support BSD
|
||||
#[pyfunction]
|
||||
#[allow(non_snake_case)]
|
||||
const fn S_ISWHT(_mode: Mode) -> bool {
|
||||
const fn S_ISWHT(_mode: ModeArg) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
#[pyfunction(name = "S_IMODE")]
|
||||
#[allow(non_snake_case)]
|
||||
const fn S_IMODE_method(mode: Mode) -> Mode {
|
||||
mode & S_IMODE
|
||||
const fn S_IMODE_method(mode: ModeArg) -> Mode {
|
||||
mode.0 & S_IMODE
|
||||
}
|
||||
|
||||
#[pyfunction(name = "S_IFMT")]
|
||||
#[allow(non_snake_case)]
|
||||
const fn S_IFMT_method(mode: Mode) -> Mode {
|
||||
const fn S_IFMT_method(mode: ModeArg) -> Mode {
|
||||
// 0o170000 is from the S_IFMT definition in CPython include/fileutils.h
|
||||
mode & S_IFMT
|
||||
mode.0 & S_IFMT
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
const fn filetype(mode: Mode) -> char {
|
||||
/// The one character a mode's file type is written as, which the module
|
||||
/// keeps to itself.
|
||||
const fn filetype(mode: ModeArg) -> char {
|
||||
if S_ISREG(mode) {
|
||||
'-'
|
||||
} else if S_ISDIR(mode) {
|
||||
@@ -491,37 +528,37 @@ mod _stat {
|
||||
|
||||
// Convert file mode to string representation
|
||||
#[pyfunction]
|
||||
fn filemode(mode: Mode) -> String {
|
||||
fn filemode(mode: ModeArg) -> String {
|
||||
let mut result = String::with_capacity(10);
|
||||
|
||||
// File type
|
||||
result.push(filetype(mode));
|
||||
|
||||
// User permissions
|
||||
result.push(if mode & S_IRUSR != 0 { 'r' } else { '-' });
|
||||
result.push(if mode & S_IWUSR != 0 { 'w' } else { '-' });
|
||||
if mode & S_ISUID != 0 {
|
||||
result.push(if mode & S_IXUSR != 0 { 's' } else { 'S' });
|
||||
result.push(if mode.0 & S_IRUSR != 0 { 'r' } else { '-' });
|
||||
result.push(if mode.0 & S_IWUSR != 0 { 'w' } else { '-' });
|
||||
if mode.0 & S_ISUID != 0 {
|
||||
result.push(if mode.0 & S_IXUSR != 0 { 's' } else { 'S' });
|
||||
} else {
|
||||
result.push(if mode & S_IXUSR != 0 { 'x' } else { '-' });
|
||||
result.push(if mode.0 & S_IXUSR != 0 { 'x' } else { '-' });
|
||||
}
|
||||
|
||||
// Group permissions
|
||||
result.push(if mode & S_IRGRP != 0 { 'r' } else { '-' });
|
||||
result.push(if mode & S_IWGRP != 0 { 'w' } else { '-' });
|
||||
if mode & S_ISGID != 0 {
|
||||
result.push(if mode & S_IXGRP != 0 { 's' } else { 'S' });
|
||||
result.push(if mode.0 & S_IRGRP != 0 { 'r' } else { '-' });
|
||||
result.push(if mode.0 & S_IWGRP != 0 { 'w' } else { '-' });
|
||||
if mode.0 & S_ISGID != 0 {
|
||||
result.push(if mode.0 & S_IXGRP != 0 { 's' } else { 'S' });
|
||||
} else {
|
||||
result.push(if mode & S_IXGRP != 0 { 'x' } else { '-' });
|
||||
result.push(if mode.0 & S_IXGRP != 0 { 'x' } else { '-' });
|
||||
}
|
||||
|
||||
// Other permissions
|
||||
result.push(if mode & S_IROTH != 0 { 'r' } else { '-' });
|
||||
result.push(if mode & S_IWOTH != 0 { 'w' } else { '-' });
|
||||
if mode & S_ISVTX != 0 {
|
||||
result.push(if mode & S_IXOTH != 0 { 't' } else { 'T' });
|
||||
result.push(if mode.0 & S_IROTH != 0 { 'r' } else { '-' });
|
||||
result.push(if mode.0 & S_IWOTH != 0 { 'w' } else { '-' });
|
||||
if mode.0 & S_ISVTX != 0 {
|
||||
result.push(if mode.0 & S_IXOTH != 0 { 't' } else { 'T' });
|
||||
} else {
|
||||
result.push(if mode & S_IXOTH != 0 { 'x' } else { '-' });
|
||||
result.push(if mode.0 & S_IXOTH != 0 { 'x' } else { '-' });
|
||||
}
|
||||
|
||||
result
|
||||
|
||||
@@ -1217,7 +1217,7 @@ mod builtins {
|
||||
#[derive(FromArgs)]
|
||||
struct ImportArgs {
|
||||
#[pyarg(any)]
|
||||
name: PyStrRef,
|
||||
name: PyObjectRef,
|
||||
#[pyarg(any, default)]
|
||||
globals: Option<PyObjectRef>,
|
||||
#[allow(dead_code)]
|
||||
@@ -1231,7 +1231,13 @@ mod builtins {
|
||||
|
||||
#[pyfunction]
|
||||
fn __import__(args: ImportArgs, vm: &VirtualMachine) -> PyResult {
|
||||
crate::import::import_module_level(&args.name, args.globals, args.fromlist, args.level, vm)
|
||||
// The name is faulted for not being a string by the import itself,
|
||||
// ahead of the level beside it. = PyImport_ImportModuleLevelObject
|
||||
let name = args
|
||||
.name
|
||||
.downcast_ref::<PyStr>()
|
||||
.ok_or_else(|| vm.new_type_error("module name must be a string"))?;
|
||||
crate::import::import_module_level(name, args.globals, args.fromlist, args.level, vm)
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
|
||||
@@ -722,6 +722,8 @@ const ERROR_CODES: &[(&str, i32)] = &[
|
||||
EPROGUNAVAIL
|
||||
),
|
||||
e!(cfg(target_vendor = "apple"), EPWROFF),
|
||||
e!(cfg(target_vendor = "apple"), EQFULL),
|
||||
e!(cfg(target_vendor = "apple"), ENOTCAPABLE),
|
||||
e!(
|
||||
cfg(any(
|
||||
target_os = "dragonfly",
|
||||
|
||||
@@ -1548,6 +1548,11 @@ pub(super) mod _os {
|
||||
|
||||
#[pyfunction]
|
||||
fn cpu_count(vm: &VirtualMachine) -> PyObjectRef {
|
||||
// A count the configuration names is answered with, over the one the
|
||||
// host reports. = os_cpu_count_impl
|
||||
if let Some(configured) = vm.state.config.settings.cpu_count {
|
||||
return vm.ctx.new_int(configured.get()).into();
|
||||
}
|
||||
let cpu_count = crate::host_env::os::cpu_count();
|
||||
vm.ctx.new_int(cpu_count).into()
|
||||
}
|
||||
|
||||
@@ -1698,6 +1698,11 @@ pub mod module {
|
||||
rustpython_host_env::posix::kill(pid, sig as i32).map_err(|err| err.into_pyexception(vm))
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn killpg(pgid: i32, sig: isize, vm: &VirtualMachine) -> PyResult<()> {
|
||||
rustpython_host_env::posix::killpg(pgid, sig as i32).map_err(|err| err.into_pyexception(vm))
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn get_terminal_size(
|
||||
fd: OptionalArg<i32>,
|
||||
|
||||
@@ -953,7 +953,7 @@ pub mod sys {
|
||||
|
||||
#[derive(FromArgs)]
|
||||
struct GetsizeofArgs {
|
||||
obj: PyObjectRef,
|
||||
object: PyObjectRef,
|
||||
#[pyarg(any, optional)]
|
||||
default: Option<PyObjectRef>,
|
||||
}
|
||||
@@ -961,7 +961,7 @@ pub mod sys {
|
||||
#[pyfunction]
|
||||
fn getsizeof(args: GetsizeofArgs, vm: &VirtualMachine) -> PyResult {
|
||||
let sizeof = || -> PyResult<usize> {
|
||||
let res = vm.call_special_method(&args.obj, identifier!(vm, __sizeof__), ())?;
|
||||
let res = vm.call_special_method(&args.object, identifier!(vm, __sizeof__), ())?;
|
||||
let res = res.try_index(vm)?.try_to_primitive::<usize>(vm)?;
|
||||
Ok(res + core::mem::size_of::<PyObject>())
|
||||
};
|
||||
@@ -1222,6 +1222,12 @@ pub mod sys {
|
||||
PyIntInfo::from_data(IntInfoData::INFO, vm)
|
||||
}
|
||||
|
||||
/// Private function for getting PyConfig.cpu_count
|
||||
#[pyfunction]
|
||||
fn _get_cpu_count_config(vm: &VirtualMachine) -> i32 {
|
||||
vm.state.config.settings.cpu_count.map_or(-1, |n| n.get())
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn get_int_max_str_digits(vm: &VirtualMachine) -> usize {
|
||||
vm.state.int_max_str_digits.load()
|
||||
|
||||
@@ -938,6 +938,9 @@ mod platform {
|
||||
#[cfg(target_os = "solaris")]
|
||||
#[pyattr]
|
||||
use libc::CLOCK_HIGHRES;
|
||||
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
|
||||
#[pyattr]
|
||||
use libc::CLOCK_MONOTONIC_RAW;
|
||||
#[cfg(not(any(
|
||||
target_os = "illumos",
|
||||
target_os = "netbsd",
|
||||
@@ -958,9 +961,12 @@ mod platform {
|
||||
use libc::CLOCK_THREAD_CPUTIME_ID;
|
||||
#[cfg(target_os = "linux")]
|
||||
#[pyattr]
|
||||
use libc::{CLOCK_BOOTTIME, CLOCK_MONOTONIC_RAW, CLOCK_TAI};
|
||||
use libc::{CLOCK_BOOTTIME, CLOCK_TAI};
|
||||
#[pyattr]
|
||||
use libc::{CLOCK_MONOTONIC, CLOCK_REALTIME};
|
||||
#[cfg(target_vendor = "apple")]
|
||||
#[pyattr]
|
||||
use libc::{CLOCK_MONOTONIC_RAW_APPROX, CLOCK_UPTIME_RAW, CLOCK_UPTIME_RAW_APPROX};
|
||||
#[cfg(any(target_os = "freebsd", target_os = "openbsd", target_os = "dragonfly"))]
|
||||
#[pyattr]
|
||||
use libc::{CLOCK_PROF, CLOCK_UPTIME};
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use core::num::NonZeroI32;
|
||||
#[cfg(feature = "flame-it")]
|
||||
use std::ffi::OsString;
|
||||
|
||||
@@ -138,6 +139,9 @@ pub struct Settings {
|
||||
/// -X int_max_str_digits
|
||||
pub int_max_str_digits: i64,
|
||||
|
||||
/// `-X cpu_count` / `PYTHON_CPU_COUNT` override; `None` leaves the count to the host
|
||||
pub cpu_count: Option<NonZeroI32>,
|
||||
|
||||
// /* --- Path configuration inputs ------------ */
|
||||
// int pathconfig_warnings;
|
||||
// wchar_t *program_name;
|
||||
@@ -217,6 +221,7 @@ impl Default for Settings {
|
||||
stdio_errors: None,
|
||||
utf8_mode: -1,
|
||||
int_max_str_digits: 4300,
|
||||
cpu_count: None,
|
||||
#[cfg(feature = "flame-it")]
|
||||
profile_output: None,
|
||||
#[cfg(feature = "flame-it")]
|
||||
|
||||
+47
-1
@@ -1,6 +1,7 @@
|
||||
use lexopt::Arg::*;
|
||||
use lexopt::ValueExt;
|
||||
use rustpython_vm::{Settings, vm::CheckHashPycsMode};
|
||||
use std::num::NonZeroI32;
|
||||
use std::str::FromStr;
|
||||
use std::{cmp, env};
|
||||
|
||||
@@ -262,6 +263,21 @@ pub fn parse_opts() -> Result<(Settings, RunMode), lexopt::Error> {
|
||||
};
|
||||
}
|
||||
|
||||
if let Some(val) = get_env("PYTHON_CPU_COUNT") {
|
||||
settings.cpu_count = match parse_cpu_count(val.to_str()) {
|
||||
Ok(cpu_count) => cpu_count,
|
||||
Err(()) => {
|
||||
error!(
|
||||
"Fatal Python error: config_init_cpu_count: \
|
||||
-X cpu_count=n option: n is missing or an invalid number, \
|
||||
n must be greater than 0\n\
|
||||
Python runtime state: preinitialized"
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
settings.check_hash_pycs_mode = args.check_hash_based_pycs;
|
||||
|
||||
if let Some(val) = get_env("PYTHONUTF8")
|
||||
@@ -308,6 +324,20 @@ pub fn parse_opts() -> Result<(Settings, RunMode), lexopt::Error> {
|
||||
}
|
||||
"no_sig_int" => settings.install_signal_handlers = false,
|
||||
"no_debug_ranges" => settings.code_debug_ranges = false,
|
||||
"cpu_count" => {
|
||||
settings.cpu_count = match parse_cpu_count(value) {
|
||||
Ok(cpu_count) => cpu_count,
|
||||
Err(()) => {
|
||||
error!(
|
||||
"Fatal Python error: config_init_cpu_count: \
|
||||
-X cpu_count=n option: n is missing or an invalid \
|
||||
number, n must be greater than 0\n\
|
||||
Python runtime state: preinitialized"
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
}
|
||||
"int_max_str_digits" => {
|
||||
settings.int_max_str_digits = match value.unwrap().parse() {
|
||||
Ok(digits) if digits == 0 || digits >= 640 => digits,
|
||||
@@ -440,7 +470,23 @@ pub fn parse_opts() -> Result<(Settings, RunMode), lexopt::Error> {
|
||||
Ok((settings, mode))
|
||||
}
|
||||
|
||||
/// Helper function to retrieve a sequence of paths from an environment variable.
|
||||
/// `-X cpu_count` / `PYTHON_CPU_COUNT`: a count above zero, or `"default"`.
|
||||
/// `Ok(None)` leaves the count to the host. Zero, negatives, and values above
|
||||
/// `i32::MAX` are rejected.
|
||||
/// = config_init_cpu_count
|
||||
fn parse_cpu_count(value: Option<&str>) -> Result<Option<NonZeroI32>, ()> {
|
||||
match value {
|
||||
None => Err(()),
|
||||
Some("default") => Ok(None),
|
||||
Some(number) => number
|
||||
.parse()
|
||||
.ok()
|
||||
.filter(|count: &NonZeroI32| count.get() > 0)
|
||||
.map(Some)
|
||||
.ok_or(()),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_paths(env_variable_name: &str) -> impl Iterator<Item = String> + '_ {
|
||||
env::var_os(env_variable_name)
|
||||
.filter(|v| !v.is_empty())
|
||||
|
||||
Reference in new issue
Block a user