Skip to content

Commit cd433c1

Browse files
committed
Merge branch 'main' into optimize-kwarg-extraction
2 parents dc17310 + cb9111b commit cd433c1

File tree

10 files changed

+80
-21
lines changed

10 files changed

+80
-21
lines changed

.github/workflows/ci.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -697,7 +697,8 @@ jobs:
697697
with:
698698
save-if: ${{ github.ref == 'refs/heads/main' || contains(github.event.pull_request.labels.*.name, 'CI-save-pr-cache') }}
699699
- run: python -m pip install --upgrade pip && pip install nox[uv]
700-
- run: nox -s test-introspection
700+
# TODO test will be fixed in https://github.com/PyO3/pyo3/pull/5450
701+
# - run: nox -s test-introspection
701702
env:
702703
CARGO_BUILD_TARGET: ${{ matrix.platform.rust-target }}
703704

newsfragments/5444.fixed.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
fix `OsStr` conversion for non-utf8 strings on windows

newsfragments/5445.added.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Implement `AsRef<[u8]>` for `PyBytes`

src/conversions/std/osstr.rs

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,12 @@ impl FromPyObject<'_, '_> for OsString {
110110
unsafe { ffi::PyUnicode_AsWideChar(pystring.as_ptr(), std::ptr::null_mut(), 0) };
111111
crate::err::error_on_minusone(ob.py(), size)?;
112112

113+
debug_assert!(
114+
size > 0,
115+
"PyUnicode_AsWideChar should return at least 1 for null terminator"
116+
);
117+
let size = size - 1; // exclude null terminator
118+
113119
let mut buffer = vec![0; size as usize];
114120
let bytes_read =
115121
unsafe { ffi::PyUnicode_AsWideChar(pystring.as_ptr(), buffer.as_mut_ptr(), size) };
@@ -169,7 +175,7 @@ impl<'py> IntoPyObject<'py> for &OsString {
169175

170176
#[cfg(test)]
171177
mod tests {
172-
use crate::types::{PyString, PyStringMethods};
178+
use crate::types::{PyAnyMethods, PyString, PyStringMethods};
173179
use crate::{BoundObject, IntoPyObject, Python};
174180
use std::fmt::Debug;
175181
use std::{
@@ -181,7 +187,6 @@ mod tests {
181187
#[cfg(not(windows))]
182188
fn test_non_utf8_conversion() {
183189
Python::attach(|py| {
184-
use crate::types::PyAnyMethods;
185190
#[cfg(not(target_os = "wasi"))]
186191
use std::os::unix::ffi::OsStrExt;
187192
#[cfg(target_os = "wasi")]
@@ -219,4 +224,32 @@ mod tests {
219224
test_roundtrip::<OsString>(py, os_str.to_os_string());
220225
});
221226
}
227+
228+
#[test]
229+
#[cfg(windows)]
230+
fn test_windows_non_utf8_osstring_roundtrip() {
231+
use std::os::windows::ffi::{OsStrExt, OsStringExt};
232+
233+
Python::attach(|py| {
234+
// Example: Unpaired surrogate (0xD800) is not valid UTF-8, but valid in Windows OsString
235+
let wide: &[u16] = &['A' as u16, 0xD800, 'B' as u16]; // 'A', unpaired surrogate, 'B'
236+
let os_str = OsString::from_wide(wide);
237+
238+
assert_eq!(os_str.to_string_lossy(), "A�B");
239+
240+
// This cannot be represented as UTF-8, so .to_str() would return None
241+
assert!(os_str.to_str().is_none());
242+
243+
// Convert to Python and back
244+
let py_str = os_str.as_os_str().into_pyobject(py).unwrap();
245+
let os_str_2 = py_str.extract::<OsString>().unwrap();
246+
247+
// The roundtrip should preserve the original wide data
248+
assert_eq!(os_str, os_str_2);
249+
250+
// Show that encode_wide is necessary: direct UTF-8 conversion would lose information
251+
let encoded: Vec<u16> = os_str.encode_wide().collect();
252+
assert_eq!(encoded, wide);
253+
});
254+
}
222255
}

src/types/bytes.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,20 @@ impl PartialEq<Borrowed<'_, '_, PyBytes>> for &'_ [u8] {
275275
}
276276
}
277277

278+
impl<'a> AsRef<[u8]> for Borrowed<'a, '_, PyBytes> {
279+
#[inline]
280+
fn as_ref(&self) -> &'a [u8] {
281+
self.as_bytes()
282+
}
283+
}
284+
285+
impl AsRef<[u8]> for Bound<'_, PyBytes> {
286+
#[inline]
287+
fn as_ref(&self) -> &[u8] {
288+
self.as_bytes()
289+
}
290+
}
291+
278292
#[cfg(test)]
279293
mod tests {
280294
use super::*;
@@ -381,4 +395,17 @@ mod tests {
381395
}
382396
})
383397
}
398+
399+
#[test]
400+
fn test_as_ref_slice() {
401+
Python::attach(|py| {
402+
let b = b"hello, world";
403+
let py_bytes = PyBytes::new(py, b);
404+
let ref_bound: &[u8] = py_bytes.as_ref();
405+
assert_eq!(ref_bound, b);
406+
let py_bytes_borrowed = py_bytes.as_borrowed();
407+
let ref_borrowed: &[u8] = py_bytes_borrowed.as_ref();
408+
assert_eq!(ref_borrowed, b);
409+
})
410+
}
384411
}

tests/ui/invalid_closure.stderr

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
error[E0597]: `local_data` does not live long enough
22
--> tests/ui/invalid_closure.rs:7:27
33
|
4-
6 | let local_data = vec![0, 1, 2, 3, 4];
4+
6 | let local_data = vec![0, 1, 2, 3, 4];
55
| ---------- binding `local_data` declared here
6-
7 | let ref_: &[u8] = &local_data;
6+
7 | let ref_: &[u8] = &local_data;
77
| ^^^^^^^^^^^ borrowed value does not live long enough
88
...
99
14 | PyCFunction::new_closure(py, None, None, closure_fn)

tests/ui/invalid_frozen_pyclass_borrow.stderr

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ error[E0271]: type mismatch resolving `<Foo as PyClass>::Frozen == False`
1313
note: expected this to be `False`
1414
--> tests/ui/invalid_frozen_pyclass_borrow.rs:3:1
1515
|
16-
3 | #[pyclass(frozen)]
16+
3 | #[pyclass(frozen)]
1717
| ^^^^^^^^^^^^^^^^^^
1818
note: required by a bound in `extract_pyclass_ref_mut`
1919
--> src/impl_/extract_argument.rs
@@ -49,7 +49,7 @@ error[E0271]: type mismatch resolving `<Foo as PyClass>::Frozen == False`
4949
note: expected this to be `False`
5050
--> tests/ui/invalid_frozen_pyclass_borrow.rs:3:1
5151
|
52-
3 | #[pyclass(frozen)]
52+
3 | #[pyclass(frozen)]
5353
| ^^^^^^^^^^^^^^^^^^
5454
note: required by a bound in `pyo3::Bound::<'py, T>::borrow_mut`
5555
--> src/instance.rs

tests/ui/invalid_pyclass_generic.stderr

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ error[E0592]: duplicate definitions with name `__pymethod___class_getitem____`
1212
error[E0592]: duplicate definitions with name `__class_getitem__`
1313
--> tests/ui/invalid_pyclass_generic.rs:4:1
1414
|
15-
4 | #[pyclass(generic)]
15+
4 | #[pyclass(generic)]
1616
| ^^^^^^^^^^^^^^^^^^^ duplicate definitions for `__class_getitem__`
1717
...
1818
15 | / pub fn __class_getitem__(
@@ -44,13 +44,13 @@ note: candidate #2 is defined in an impl for the type `ClassRedefinesClassGetIte
4444
error[E0034]: multiple applicable items in scope
4545
--> tests/ui/invalid_pyclass_generic.rs:4:1
4646
|
47-
4 | #[pyclass(generic)]
47+
4 | #[pyclass(generic)]
4848
| ^^^^^^^^^^^^^^^^^^^ multiple `__class_getitem__` found
4949
|
5050
note: candidate #1 is defined in an impl for the type `ClassRedefinesClassGetItem`
5151
--> tests/ui/invalid_pyclass_generic.rs:4:1
5252
|
53-
4 | #[pyclass(generic)]
53+
4 | #[pyclass(generic)]
5454
| ^^^^^^^^^^^^^^^^^^^
5555
note: candidate #2 is defined in an impl for the type `ClassRedefinesClassGetItem`
5656
--> tests/ui/invalid_pyclass_generic.rs:15:5
@@ -70,8 +70,6 @@ error[E0034]: multiple applicable items in scope
7070
|
7171
= note: candidate #1 is defined in an impl for the type `IntoPyObjectConverter<Result<T, E>>`
7272
= note: candidate #2 is defined in an impl for the type `IntoPyObjectConverter<T>`
73-
= note: candidate #3 is defined in an impl for the type `UnknownReturnResultType<Result<T, E>>`
74-
= note: candidate #4 is defined in an impl for the type `UnknownReturnType<T>`
7573
= note: this error originates in the attribute macro `pyclass` (in Nightly builds, run with -Z macro-backtrace for more info)
7674

7775
error[E0308]: mismatched types
@@ -94,12 +92,12 @@ error[E0034]: multiple applicable items in scope
9492
note: candidate #1 is defined in an impl for the type `ClassRedefinesClassGetItem`
9593
--> tests/ui/invalid_pyclass_generic.rs:4:1
9694
|
97-
4 | #[pyclass(generic)]
95+
4 | #[pyclass(generic)]
9896
| ^^^^^^^^^^^^^^^^^^^
9997
note: candidate #2 is defined in an impl for the type `ClassRedefinesClassGetItem`
10098
--> tests/ui/invalid_pyclass_generic.rs:7:1
10199
|
102-
7 | #[pymethods]
100+
7 | #[pymethods]
103101
| ^^^^^^^^^^^^
104102
= note: this error originates in the attribute macro `pyclass` which comes from the expansion of the attribute macro `pymethods` (in Nightly builds, run with -Z macro-backtrace for more info)
105103

@@ -112,7 +110,7 @@ error[E0034]: multiple applicable items in scope
112110
note: candidate #1 is defined in an impl for the type `ClassRedefinesClassGetItem`
113111
--> tests/ui/invalid_pyclass_generic.rs:4:1
114112
|
115-
4 | #[pyclass(generic)]
113+
4 | #[pyclass(generic)]
116114
| ^^^^^^^^^^^^^^^^^^^
117115
note: candidate #2 is defined in an impl for the type `ClassRedefinesClassGetItem`
118116
--> tests/ui/invalid_pyclass_generic.rs:15:5
@@ -132,5 +130,3 @@ error[E0034]: multiple applicable items in scope
132130
|
133131
= note: candidate #1 is defined in an impl for the type `IntoPyObjectConverter<Result<T, E>>`
134132
= note: candidate #2 is defined in an impl for the type `IntoPyObjectConverter<T>`
135-
= note: candidate #3 is defined in an impl for the type `UnknownReturnResultType<Result<T, E>>`
136-
= note: candidate #4 is defined in an impl for the type `UnknownReturnType<T>`

tests/ui/invalid_pymethod_enum.stderr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ error[E0271]: type mismatch resolving `<ComplexEnum as PyClass>::Frozen == False
77
note: expected this to be `False`
88
--> tests/ui/invalid_pymethod_enum.rs:3:1
99
|
10-
3 | #[pyclass]
10+
3 | #[pyclass]
1111
| ^^^^^^^^^^
1212
note: required by a bound in `extract_pyclass_ref_mut`
1313
--> src/impl_/extract_argument.rs

tests/ui/not_send2.stderr

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
error[E0277]: `*mut pyo3::Python<'static>` cannot be shared between threads safely
22
--> tests/ui/not_send2.rs:8:19
33
|
4-
8 | py.detach(|| {
4+
8 | py.detach(|| {
55
| ____________------_^
66
| | |
77
| | required by a bound introduced by this call
8-
9 | | println!("{:?}", string);
8+
9 | | println!("{:?}", string);
99
10 | | });
1010
| |_________^ `*mut pyo3::Python<'static>` cannot be shared between threads safely
1111
|
@@ -39,7 +39,7 @@ note: required because it appears within the type `pyo3::Bound<'_, PyString>`
3939
note: required because it's used within this closure
4040
--> tests/ui/not_send2.rs:8:19
4141
|
42-
8 | py.detach(|| {
42+
8 | py.detach(|| {
4343
| ^^
4444
= note: required for `{closure@$DIR/tests/ui/not_send2.rs:8:19: 8:21}` to implement `Ungil`
4545
note: required by a bound in `pyo3::Python::<'py>::detach`

0 commit comments

Comments
 (0)