Skip to content

Commit 4f40fa1

Browse files
authored
Merge pull request RustPython#1863 from youknowone/single_or_tuple_any
Fix bytes.{start|end}swith
2 parents 1ecd94b + 4c9486f commit 4f40fa1

File tree

9 files changed

+214
-188
lines changed

9 files changed

+214
-188
lines changed

Lib/test/test_bytes.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -548,8 +548,6 @@ def test_count(self):
548548
self.assertEqual(b.count(i, 1, 3), 1)
549549
self.assertEqual(b.count(p, 7, 9), 1)
550550

551-
# TODO: RUSTPYTHON
552-
@unittest.expectedFailure
553551
def test_startswith(self):
554552
b = self.type2test(b'hello')
555553
self.assertFalse(self.type2test().startswith(b"anything"))
@@ -564,8 +562,6 @@ def test_startswith(self):
564562
self.assertIn('bytes', exc)
565563
self.assertIn('tuple', exc)
566564

567-
# TODO: RUSTPYTHON
568-
@unittest.expectedFailure
569565
def test_endswith(self):
570566
b = self.type2test(b'hello')
571567
self.assertFalse(bytearray().endswith(b"anything"))

vm/src/builtins.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -363,10 +363,14 @@ fn builtin_input(prompt: OptionalArg<PyStringRef>, vm: &VirtualMachine) -> PyRes
363363
}
364364
}
365365

366-
fn builtin_isinstance(obj: PyObjectRef, typ: PyObjectRef, vm: &VirtualMachine) -> PyResult<bool> {
366+
pub fn builtin_isinstance(
367+
obj: PyObjectRef,
368+
typ: PyObjectRef,
369+
vm: &VirtualMachine,
370+
) -> PyResult<bool> {
367371
single_or_tuple_any(
368372
typ,
369-
|cls: PyClassRef| vm.isinstance(&obj, &cls),
373+
|cls: &PyClassRef| vm.isinstance(&obj, cls),
370374
|o| {
371375
format!(
372376
"isinstance() arg 2 must be a type or tuple of types, not {}",
@@ -384,7 +388,7 @@ fn builtin_issubclass(
384388
) -> PyResult<bool> {
385389
single_or_tuple_any(
386390
typ,
387-
|cls: PyClassRef| vm.issubclass(&subclass, &cls),
391+
|cls: &PyClassRef| vm.issubclass(&subclass, cls),
388392
|o| {
389393
format!(
390394
"issubclass() arg 2 must be a class or tuple of classes, not {}",

vm/src/frame.rs

Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@ use std::sync::Mutex;
55
use indexmap::IndexMap;
66
use itertools::Itertools;
77

8+
use crate::builtins::builtin_isinstance;
89
use crate::bytecode;
910
use crate::exceptions::{self, ExceptionCtor, PyBaseExceptionRef};
10-
use crate::function::{single_or_tuple_any, PyFuncArgs};
11+
use crate::function::PyFuncArgs;
1112
use crate::obj::objasyncgenerator::PyAsyncGenWrappedValue;
1213
use crate::obj::objbool;
1314
use crate::obj::objcode::PyCodeRef;
@@ -1353,25 +1354,6 @@ impl ExecutingFrame<'_> {
13531354
!a.is(&b)
13541355
}
13551356

1356-
fn exc_match(
1357-
&self,
1358-
vm: &VirtualMachine,
1359-
exc: PyObjectRef,
1360-
exc_type: PyObjectRef,
1361-
) -> PyResult<bool> {
1362-
single_or_tuple_any(
1363-
exc_type,
1364-
|cls: PyClassRef| vm.isinstance(&exc, &cls),
1365-
|o| {
1366-
format!(
1367-
"isinstance() arg 2 must be a type or tuple of types, not {}",
1368-
o.class()
1369-
)
1370-
},
1371-
vm,
1372-
)
1373-
}
1374-
13751357
#[cfg_attr(feature = "flame-it", flame("Frame"))]
13761358
fn execute_compare(
13771359
&mut self,
@@ -1391,7 +1373,9 @@ impl ExecutingFrame<'_> {
13911373
bytecode::ComparisonOperator::IsNot => vm.new_bool(self._is_not(a, b)),
13921374
bytecode::ComparisonOperator::In => vm.new_bool(self._in(vm, a, b)?),
13931375
bytecode::ComparisonOperator::NotIn => vm.new_bool(self._not_in(vm, a, b)?),
1394-
bytecode::ComparisonOperator::ExceptionMatch => vm.new_bool(self.exc_match(vm, a, b)?),
1376+
bytecode::ComparisonOperator::ExceptionMatch => {
1377+
vm.new_bool(builtin_isinstance(a, b, vm)?)
1378+
}
13951379
};
13961380

13971381
self.push_value(value);

vm/src/function.rs

Lines changed: 33 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use result_like::impl_option_like;
77
use smallbox::{smallbox, space::S1, SmallBox};
88

99
use crate::exceptions::PyBaseExceptionRef;
10-
use crate::obj::objtuple::PyTuple;
10+
use crate::obj::objtuple::PyTupleRef;
1111
use crate::obj::objtype::{isinstance, PyClassRef};
1212
use crate::pyobject::{
1313
IntoPyObject, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, TypeProtocol,
@@ -564,43 +564,55 @@ into_py_native_func_tuple!((a, A), (b, B), (c, C), (d, D), (e, E));
564564
/// test that any of the values contained within the tuples satisfies the predicate. Type parameter
565565
/// T specifies the type that is expected, if the input value is not of that type or a tuple of
566566
/// values of that type, then a TypeError is raised.
567-
pub fn single_or_tuple_any<T: PyValue, F: Fn(PyRef<T>) -> PyResult<bool>>(
567+
pub fn single_or_tuple_any<T, F, M>(
568568
obj: PyObjectRef,
569569
predicate: F,
570-
message: fn(&PyObjectRef) -> String,
570+
message: M,
571571
vm: &VirtualMachine,
572-
) -> PyResult<bool> {
572+
) -> PyResult<bool>
573+
where
574+
T: TryFromObject,
575+
F: Fn(&T) -> PyResult<bool>,
576+
M: Fn(&PyObjectRef) -> String,
577+
{
573578
// TODO: figure out some way to have recursive calls without... this
574-
use std::marker::PhantomData;
575-
struct Checker<'vm, T: PyValue, F: Fn(PyRef<T>) -> PyResult<bool>> {
579+
struct Checker<T, F, M>
580+
where
581+
F: Fn(&T) -> PyResult<bool>,
582+
M: Fn(&PyObjectRef) -> String,
583+
{
576584
predicate: F,
577-
message: fn(&PyObjectRef) -> String,
578-
vm: &'vm VirtualMachine,
579-
t: PhantomData<T>,
580-
}
581-
impl<T: PyValue, F: Fn(PyRef<T>) -> PyResult<bool>> Checker<'_, T, F> {
582-
fn check(&self, obj: PyObjectRef) -> PyResult<bool> {
583-
match_class!(match obj {
584-
obj @ T => (self.predicate)(obj),
585-
tuple @ PyTuple => {
585+
message: M,
586+
t: std::marker::PhantomData<T>,
587+
}
588+
impl<T, F, M> Checker<T, F, M>
589+
where
590+
T: TryFromObject,
591+
F: Fn(&T) -> PyResult<bool>,
592+
M: Fn(&PyObjectRef) -> String,
593+
{
594+
fn check(&self, obj: &PyObjectRef, vm: &VirtualMachine) -> PyResult<bool> {
595+
match T::try_from_object(vm, obj.clone()) {
596+
Ok(single) => (self.predicate)(&single),
597+
Err(_) => {
598+
let tuple = PyTupleRef::try_from_object(vm, obj.clone())
599+
.map_err(|_| vm.new_type_error((self.message)(&obj)))?;
586600
for obj in tuple.as_slice().iter() {
587-
if self.check(obj.clone())? {
601+
if self.check(&obj, vm)? {
588602
return Ok(true);
589603
}
590604
}
591605
Ok(false)
592606
}
593-
obj => Err(self.vm.new_type_error((self.message)(&obj))),
594-
})
607+
}
595608
}
596609
}
597610
let checker = Checker {
598611
predicate,
599612
message,
600-
vm,
601-
t: PhantomData,
613+
t: std::marker::PhantomData,
602614
};
603-
checker.check(obj)
615+
checker.check(&obj, vm)
604616
}
605617

606618
#[cfg(test)]

vm/src/obj/objbytearray.rs

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ use super::objint::PyIntRef;
1212
use super::objiter;
1313
use super::objslice::PySliceRef;
1414
use super::objstr::{PyString, PyStringRef};
15-
use super::objtuple::PyTupleRef;
1615
use super::objtype::PyClassRef;
16+
use super::pystr::PyCommonString;
1717
use crate::cformat::CFormatString;
1818
use crate::function::{OptionalArg, OptionalOption};
1919
use crate::obj::objstr::do_cformat_string;
@@ -303,25 +303,39 @@ impl PyByteArray {
303303
#[pymethod(name = "endswith")]
304304
fn endswith(
305305
&self,
306-
suffix: Either<PyByteInner, PyTupleRef>,
307-
start: OptionalArg<PyObjectRef>,
308-
end: OptionalArg<PyObjectRef>,
306+
suffix: PyObjectRef,
307+
start: OptionalArg<Option<isize>>,
308+
end: OptionalArg<Option<isize>>,
309309
vm: &VirtualMachine,
310310
) -> PyResult<bool> {
311-
self.borrow_value()
312-
.startsendswith(suffix, start, end, true, vm)
311+
self.borrow_value().elements[..].py_startsendswith(
312+
suffix,
313+
start,
314+
end,
315+
"endswith",
316+
"bytes",
317+
|s, x: &PyByteInner| s.ends_with(&x.elements[..]),
318+
vm,
319+
)
313320
}
314321

315322
#[pymethod(name = "startswith")]
316323
fn startswith(
317324
&self,
318-
prefix: Either<PyByteInner, PyTupleRef>,
319-
start: OptionalArg<PyObjectRef>,
320-
end: OptionalArg<PyObjectRef>,
325+
prefix: PyObjectRef,
326+
start: OptionalArg<Option<isize>>,
327+
end: OptionalArg<Option<isize>>,
321328
vm: &VirtualMachine,
322329
) -> PyResult<bool> {
323-
self.borrow_value()
324-
.startsendswith(prefix, start, end, false, vm)
330+
self.borrow_value().elements[..].py_startsendswith(
331+
prefix,
332+
start,
333+
end,
334+
"startswith",
335+
"bytes",
336+
|s, x: &PyByteInner| s.starts_with(&x.elements[..]),
337+
vm,
338+
)
325339
}
326340

327341
#[pymethod(name = "find")]

vm/src/obj/objbyteinner.rs

Lines changed: 12 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,10 @@ use super::objint::{self, PyInt, PyIntRef};
1111
use super::objlist::PyList;
1212
use super::objmemory::PyMemoryView;
1313
use super::objnone::PyNoneRef;
14-
use super::objsequence::{is_valid_slice_arg, PySliceableSequence};
14+
use super::objsequence::PySliceableSequence;
1515
use super::objslice::PySliceRef;
16-
use super::objstr::{self, adjust_indices, PyString, PyStringRef, StringRange};
17-
use super::objtuple::PyTupleRef;
18-
use super::pystr::PyCommonString;
16+
use super::objstr::{self, PyString, PyStringRef};
17+
use super::pystr::{self, PyCommonString, StringRange};
1918
use crate::function::{OptionalArg, OptionalOption};
2019
use crate::pyhash;
2120
use crate::pyobject::{
@@ -172,7 +171,7 @@ impl ByteInnerFindOptions {
172171
Either::A(v) => v.elements.to_vec(),
173172
Either::B(int) => vec![int.as_bigint().byte_or(vm)?],
174173
};
175-
let range = adjust_indices(self.start, self.end, len);
174+
let range = pystr::adjust_indices(self.start, self.end, len);
176175
Ok((sub, range))
177176
}
178177
}
@@ -856,47 +855,6 @@ impl PyByteInner {
856855
Ok(refs)
857856
}
858857

859-
#[inline]
860-
pub fn startsendswith(
861-
&self,
862-
arg: Either<PyByteInner, PyTupleRef>,
863-
start: OptionalArg<PyObjectRef>,
864-
end: OptionalArg<PyObjectRef>,
865-
endswith: bool, // true for endswith, false for startswith
866-
vm: &VirtualMachine,
867-
) -> PyResult<bool> {
868-
let suff = match arg {
869-
Either::A(byte) => byte.elements,
870-
Either::B(tuple) => {
871-
let mut flatten = vec![];
872-
for v in tuple.as_slice() {
873-
flatten.extend(PyByteInner::try_from_object(vm, v.clone())?.elements)
874-
}
875-
flatten
876-
}
877-
};
878-
879-
if suff.is_empty() {
880-
return Ok(true);
881-
}
882-
let range = self.elements.get_slice_range(
883-
&is_valid_slice_arg(start, vm)?,
884-
&is_valid_slice_arg(end, vm)?,
885-
);
886-
887-
if range.end - range.start < suff.len() {
888-
return Ok(false);
889-
}
890-
891-
let offset = if endswith {
892-
(range.end - suff.len())..range.end
893-
} else {
894-
0..suff.len()
895-
};
896-
897-
Ok(suff.as_slice() == &self.elements.do_slice(range)[offset])
898-
}
899-
900858
#[inline]
901859
pub fn find(
902860
&self,
@@ -1342,6 +1300,14 @@ pub fn bytes_zfill(bytes: &[u8], width: usize) -> Vec<u8> {
13421300
const ASCII_WHITESPACES: [u8; 6] = [0x20, 0x09, 0x0a, 0x0c, 0x0d, 0x0b];
13431301

13441302
impl PyCommonString<'_, u8> for [u8] {
1303+
fn get_slice(&self, range: std::ops::Range<usize>) -> &Self {
1304+
&self[range]
1305+
}
1306+
1307+
fn len(&self) -> usize {
1308+
Self::len(self)
1309+
}
1310+
13451311
fn py_split_whitespace<F>(&self, maxsplit: isize, convert: F) -> Vec<PyObjectRef>
13461312
where
13471313
F: Fn(&Self) -> PyObjectRef,

vm/src/obj/objbytes.rs

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use crossbeam_utils::atomic::AtomicCell;
22
use std::mem::size_of;
33
use std::ops::Deref;
4+
use std::str::FromStr;
45

56
use super::objbyteinner::{
67
ByteInnerExpandtabsOptions, ByteInnerFindOptions, ByteInnerNewOptions, ByteInnerPaddingOptions,
@@ -10,8 +11,8 @@ use super::objint::PyIntRef;
1011
use super::objiter;
1112
use super::objslice::PySliceRef;
1213
use super::objstr::{PyString, PyStringRef};
13-
use super::objtuple::PyTupleRef;
1414
use super::objtype::PyClassRef;
15+
use super::pystr::PyCommonString;
1516
use crate::cformat::CFormatString;
1617
use crate::function::{OptionalArg, OptionalOption};
1718
use crate::obj::objstr::do_cformat_string;
@@ -23,7 +24,6 @@ use crate::pyobject::{
2324
ThreadSafe, TryFromObject, TypeProtocol,
2425
};
2526
use crate::vm::VirtualMachine;
26-
use std::str::FromStr;
2727

2828
/// "bytes(iterable_of_ints) -> bytes\n\
2929
/// bytes(string, encoding[, errors]) -> bytes\n\
@@ -275,23 +275,39 @@ impl PyBytes {
275275
#[pymethod(name = "endswith")]
276276
fn endswith(
277277
&self,
278-
suffix: Either<PyByteInner, PyTupleRef>,
279-
start: OptionalArg<PyObjectRef>,
280-
end: OptionalArg<PyObjectRef>,
278+
suffix: PyObjectRef,
279+
start: OptionalArg<Option<isize>>,
280+
end: OptionalArg<Option<isize>>,
281281
vm: &VirtualMachine,
282282
) -> PyResult<bool> {
283-
self.inner.startsendswith(suffix, start, end, true, vm)
283+
self.inner.elements[..].py_startsendswith(
284+
suffix,
285+
start,
286+
end,
287+
"endswith",
288+
"bytes",
289+
|s, x: &PyByteInner| s.ends_with(&x.elements[..]),
290+
vm,
291+
)
284292
}
285293

286294
#[pymethod(name = "startswith")]
287295
fn startswith(
288296
&self,
289-
prefix: Either<PyByteInner, PyTupleRef>,
290-
start: OptionalArg<PyObjectRef>,
291-
end: OptionalArg<PyObjectRef>,
297+
prefix: PyObjectRef,
298+
start: OptionalArg<Option<isize>>,
299+
end: OptionalArg<Option<isize>>,
292300
vm: &VirtualMachine,
293301
) -> PyResult<bool> {
294-
self.inner.startsendswith(prefix, start, end, false, vm)
302+
self.inner.elements[..].py_startsendswith(
303+
prefix,
304+
start,
305+
end,
306+
"startswith",
307+
"bytes",
308+
|s, x: &PyByteInner| s.starts_with(&x.elements[..]),
309+
vm,
310+
)
295311
}
296312

297313
#[pymethod(name = "find")]

0 commit comments

Comments
 (0)