Skip to content

Commit 5f34c61

Browse files
authored
Merge pull request RustPython#1957 from RustPython/coolreader18/json-encode_basestring
Implement _json.encode_basestring{,_ascii}
2 parents 569031d + 74f261a commit 5f34c61

File tree

13 files changed

+232
-23
lines changed

13 files changed

+232
-23
lines changed

Lib/test/test_json/test_speedups.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@ def test_scanstring(self):
1414
self.assertEqual(self.json.decoder.scanstring.__module__, "_json")
1515
self.assertIs(self.json.decoder.scanstring, self.json.decoder.c_scanstring)
1616

17-
# TODO: RUSTPYTHON
18-
@unittest.expectedFailure
1917
def test_encode_basestring_ascii(self):
2018
self.assertEqual(self.json.encoder.encode_basestring_ascii.__module__,
2119
"_json")

compiler/src/compile.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -591,7 +591,7 @@ impl<O: OutputStream> Compiler<O> {
591591
{
592592
return Err(self.error_loc(
593593
CompileErrorType::AsyncReturnValue,
594-
statement.location.clone(),
594+
statement.location,
595595
));
596596
}
597597
self.compile_expression(v)?;

vm/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ libz-sys = "1.0"
120120
winreg = "0.7"
121121
schannel = "0.1"
122122

123-
[target."cfg(windows)".dependencies.winapi]
123+
[target.'cfg(windows)'.dependencies.winapi]
124124
version = "0.3"
125125
features = ["winsock2", "handleapi", "ws2def", "std", "winbase", "wincrypt", "fileapi"]
126126

vm/src/dictdatatype.rs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::obj::objstr::PyString;
1+
use crate::obj::objstr::{PyString, PyStringRef};
22
use crate::pyhash;
33
use crate::pyobject::{IdProtocol, IntoPyObject, PyObjectRef, PyResult};
44
use crate::vm::VirtualMachine;
@@ -438,6 +438,26 @@ impl DictKey for &PyObjectRef {
438438
}
439439
}
440440

441+
impl DictKey for &PyStringRef {
442+
fn do_hash(self, _vm: &VirtualMachine) -> PyResult<HashValue> {
443+
Ok(self.hash())
444+
}
445+
446+
fn do_is(self, other: &PyObjectRef) -> bool {
447+
self.is(other)
448+
}
449+
450+
fn do_eq(self, vm: &VirtualMachine, other_key: &PyObjectRef) -> PyResult<bool> {
451+
if self.is(other_key) {
452+
Ok(true)
453+
} else if let Some(py_str_value) = other_key.payload::<PyString>() {
454+
Ok(py_str_value.as_str() == self.as_str())
455+
} else {
456+
vm.bool_eq(self.clone().into_object(), other_key.clone())
457+
}
458+
}
459+
}
460+
441461
/// Implement trait for the str type, so that we can use strings
442462
/// to index dictionaries.
443463
impl DictKey for &str {

vm/src/macros.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,3 +328,17 @@ macro_rules! class_or_notimplemented {
328328
}
329329
};
330330
}
331+
332+
#[macro_export]
333+
macro_rules! named_function {
334+
($ctx:expr, $module:ident, $func:ident) => {{
335+
paste::expr! {
336+
$crate::pyobject::PyContext::new_function_named(
337+
&$ctx,
338+
[<$module _ $func>],
339+
stringify!($module).to_owned(),
340+
stringify!($func).to_owned(),
341+
)
342+
}
343+
}};
344+
}

vm/src/obj/objbuiltinfunc.rs

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use std::fmt;
22

33
use crate::function::{OptionalArg, PyFuncArgs, PyNativeFunc};
4+
use crate::obj::objstr::PyStringRef;
45
use crate::obj::objtype::PyClassRef;
56
use crate::pyobject::{
67
IdProtocol, PyClassImpl, PyContext, PyObjectRef, PyResult, PyValue, TypeProtocol,
@@ -11,12 +12,15 @@ use crate::vm::VirtualMachine;
1112
#[pyclass]
1213
pub struct PyBuiltinFunction {
1314
value: PyNativeFunc,
15+
module: Option<PyStringRef>,
16+
name: Option<PyStringRef>,
1417
}
1518

1619
impl PyValue for PyBuiltinFunction {
1720
fn class(vm: &VirtualMachine) -> PyClassRef {
1821
vm.ctx.builtin_function_or_method_type()
1922
}
23+
const HAVE_DICT: bool = true;
2024
}
2125

2226
impl fmt::Debug for PyBuiltinFunction {
@@ -27,7 +31,19 @@ impl fmt::Debug for PyBuiltinFunction {
2731

2832
impl PyBuiltinFunction {
2933
pub fn new(value: PyNativeFunc) -> Self {
30-
Self { value }
34+
Self {
35+
value,
36+
module: None,
37+
name: None,
38+
}
39+
}
40+
41+
pub fn new_with_name(value: PyNativeFunc, module: PyStringRef, name: PyStringRef) -> Self {
42+
Self {
43+
value,
44+
module: Some(module),
45+
name: Some(name),
46+
}
3147
}
3248

3349
pub fn as_func(&self) -> &PyNativeFunc {
@@ -42,7 +58,16 @@ impl SlotCall for PyBuiltinFunction {
4258
}
4359

4460
#[pyimpl(with(SlotCall))]
45-
impl PyBuiltinFunction {}
61+
impl PyBuiltinFunction {
62+
#[pyproperty(magic)]
63+
fn module(&self) -> Option<PyStringRef> {
64+
self.module.clone()
65+
}
66+
#[pyproperty(magic)]
67+
fn name(&self) -> Option<PyStringRef> {
68+
self.name.clone()
69+
}
70+
}
4671

4772
#[pyclass]
4873
pub struct PyBuiltinMethod {
@@ -64,7 +89,12 @@ impl fmt::Debug for PyBuiltinMethod {
6489
impl PyBuiltinMethod {
6590
pub fn new(value: PyNativeFunc) -> Self {
6691
Self {
67-
function: PyBuiltinFunction { value },
92+
function: PyBuiltinFunction::new(value),
93+
}
94+
}
95+
pub fn new_with_name(value: PyNativeFunc, module: PyStringRef, name: PyStringRef) -> Self {
96+
Self {
97+
function: PyBuiltinFunction::new_with_name(value, module, name),
6898
}
6999
}
70100

@@ -100,9 +130,14 @@ impl SlotCall for PyBuiltinMethod {
100130

101131
#[pyimpl(with(SlotDescriptor, SlotCall))]
102132
impl PyBuiltinMethod {
103-
// TODO: give builtin functions names
104133
#[pyproperty(magic)]
105-
fn name(&self) {}
134+
fn module(&self) -> Option<PyStringRef> {
135+
self.function.module.clone()
136+
}
137+
#[pyproperty(magic)]
138+
fn name(&self) -> Option<PyStringRef> {
139+
self.function.name.clone()
140+
}
106141
}
107142

108143
pub fn init(context: &PyContext) {

vm/src/obj/objstr.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,7 @@ impl PyString {
292292
}
293293

294294
#[pymethod(name = "__hash__")]
295-
fn hash(&self) -> pyhash::PyHash {
295+
pub(crate) fn hash(&self) -> pyhash::PyHash {
296296
self.hash.load().unwrap_or_else(|| {
297297
let hash = pyhash::hash_value(&self.value);
298298
self.hash.store(Some(hash));

vm/src/obj/objtype.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,7 @@ impl PyClassRef {
274274
// Search the bases for the proper metatype to deal with this:
275275
let winner = calculate_meta_class(metatype.clone(), &bases, vm)?;
276276
let metatype = if !winner.is(&metatype) {
277+
#[allow(clippy::redundant_clone)] // false positive
277278
if let Some(ref tp_new) = winner.clone().slots.read().unwrap().new {
278279
// Pass it to the winner
279280

vm/src/pyobject.rs

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ impl PyContext {
138138
let exceptions = exceptions::ExceptionZoo::new(&types.type_type, &types.object_type);
139139

140140
fn create_object<T: PyObjectPayload + PyValue>(payload: T, cls: &PyClassRef) -> PyRef<T> {
141-
PyRef::new_ref_unchecked(PyObject::new(payload, cls.clone(), None))
141+
PyRef::from_obj_unchecked(PyObject::new(payload, cls.clone(), None))
142142
}
143143

144144
let none_type = create_type("NoneType", &types.type_type, &types.object_type);
@@ -492,6 +492,18 @@ impl PyContext {
492492
)
493493
}
494494

495+
pub fn new_function_named<F, T, R, VM>(&self, f: F, module: String, name: String) -> PyObjectRef
496+
where
497+
F: IntoPyNativeFunc<T, R, VM>,
498+
{
499+
let stringref = |s| PyRef::new_ref(objstr::PyString::from(s), self.str_type(), None);
500+
PyObject::new(
501+
PyBuiltinFunction::new_with_name(f.into_func(), stringref(module), stringref(name)),
502+
self.builtin_function_or_method_type(),
503+
None,
504+
)
505+
}
506+
495507
pub fn new_method<F, T, R, VM>(&self, f: F) -> PyObjectRef
496508
where
497509
F: IntoPyNativeFunc<T, R, VM>,
@@ -587,7 +599,7 @@ impl PyContext {
587599
bytecode::Constant::Complex { ref value } => self.new_complex(*value),
588600
bytecode::Constant::String { ref value } => self.new_str(value.clone()),
589601
bytecode::Constant::Bytes { ref value } => self.new_bytes(value.clone()),
590-
bytecode::Constant::Boolean { ref value } => self.new_bool(value.clone()),
602+
bytecode::Constant::Boolean { value } => self.new_bool(value),
591603
bytecode::Constant::Code { ref code } => {
592604
self.new_code_object(*code.clone()).into_object()
593605
}
@@ -695,9 +707,14 @@ impl<T> Clone for PyRef<T> {
695707
}
696708

697709
impl<T: PyValue> PyRef<T> {
698-
fn new_ref(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult<Self> {
710+
#[allow(clippy::new_ret_no_self)]
711+
pub fn new_ref(payload: T, typ: PyClassRef, dict: Option<PyDictRef>) -> Self {
712+
Self::from_obj_unchecked(PyObject::new(payload, typ, dict))
713+
}
714+
715+
fn from_obj(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult<Self> {
699716
if obj.payload_is::<T>() {
700-
Ok(Self::new_ref_unchecked(obj))
717+
Ok(Self::from_obj_unchecked(obj))
701718
} else {
702719
Err(vm.new_runtime_error(format!(
703720
"Unexpected payload for type {:?}",
@@ -706,7 +723,7 @@ impl<T: PyValue> PyRef<T> {
706723
}
707724
}
708725

709-
pub(crate) fn new_ref_unchecked(obj: PyObjectRef) -> Self {
726+
pub(crate) fn from_obj_unchecked(obj: PyObjectRef) -> Self {
710727
PyRef {
711728
obj,
712729
_payload: PhantomData,
@@ -747,7 +764,7 @@ where
747764
{
748765
fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
749766
if objtype::isinstance(&obj, &T::class(vm)) {
750-
PyRef::new_ref(obj, vm)
767+
PyRef::from_obj(obj, vm)
751768
} else {
752769
let class = T::class(vm);
753770
let expected_type = vm.to_pystr(&class)?;
@@ -1084,6 +1101,12 @@ impl<T> IntoPyObject for PyRef<T> {
10841101
}
10851102
}
10861103

1104+
impl<T> IntoPyObject for &PyRef<T> {
1105+
fn into_pyobject(self, _vm: &VirtualMachine) -> PyResult {
1106+
Ok(self.obj.clone())
1107+
}
1108+
}
1109+
10871110
impl IntoPyObject for PyCallable {
10881111
fn into_pyobject(self, _vm: &VirtualMachine) -> PyResult {
10891112
Ok(self.into_object())
@@ -1145,7 +1168,7 @@ where
11451168
where
11461169
T: PyValue,
11471170
{
1148-
PyRef::new_ref_unchecked(self as PyObjectRef)
1171+
PyRef::from_obj_unchecked(self as PyObjectRef)
11491172
}
11501173
}
11511174

@@ -1210,7 +1233,7 @@ pub trait PyValue: fmt::Debug + Send + Sync + Sized + 'static {
12101233
} else {
12111234
Some(vm.ctx.new_dict())
12121235
};
1213-
PyRef::new_ref(PyObject::new(self, cls, dict), vm)
1236+
PyRef::from_obj(PyObject::new(self, cls, dict), vm)
12141237
} else {
12151238
let subtype = vm.to_str(&cls.obj)?;
12161239
let basetype = vm.to_str(&class.obj)?;
@@ -1219,7 +1242,7 @@ pub trait PyValue: fmt::Debug + Send + Sync + Sized + 'static {
12191242
}
12201243

12211244
fn into_ref_with_type_unchecked(self, cls: PyClassRef, dict: Option<PyDictRef>) -> PyRef<Self> {
1222-
PyRef::new_ref_unchecked(PyObject::new(self, cls, dict))
1245+
PyRef::from_obj_unchecked(PyObject::new(self, cls, dict))
12231246
}
12241247
}
12251248

vm/src/scope.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ impl Scope {
3939
) -> Scope {
4040
if !globals.contains_key("__builtins__", vm) {
4141
globals
42-
.clone()
4342
.set_item("__builtins__", vm.builtins.clone(), vm)
4443
.unwrap();
4544
}

vm/src/stdlib/json.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ use crate::VirtualMachine;
77
use num_bigint::BigInt;
88
use std::str::FromStr;
99

10+
mod machinery;
11+
1012
#[pyclass(name = "Scanner")]
1113
#[derive(Debug)]
1214
struct JsonScanner {
@@ -209,11 +211,30 @@ impl JsonScanner {
209211
}
210212
}
211213

214+
fn encode_string(s: &str, ascii_only: bool) -> String {
215+
let mut buf = Vec::<u8>::with_capacity(s.len() + 2);
216+
machinery::write_json_string(s, ascii_only, &mut buf)
217+
// writing to a vec can't fail
218+
.unwrap_or_else(|_| unsafe { std::hint::unreachable_unchecked() });
219+
// TODO: verify that the implementation is correct enough to use `from_utf8_unchecked`
220+
String::from_utf8(buf).expect("invalid utf-8 in json output")
221+
}
222+
223+
fn _json_encode_basestring(s: PyStringRef) -> String {
224+
encode_string(s.as_str(), false)
225+
}
226+
227+
fn _json_encode_basestring_ascii(s: PyStringRef) -> String {
228+
encode_string(s.as_str(), true)
229+
}
230+
212231
pub fn make_module(vm: &VirtualMachine) -> PyObjectRef {
213232
let ctx = &vm.ctx;
214233
let scanner_cls = JsonScanner::make_class(ctx);
215234
scanner_cls.set_str_attr("__module__", vm.new_str("_json".to_owned()));
216235
py_module!(vm, "_json", {
217236
"make_scanner" => scanner_cls,
237+
"encode_basestring" => named_function!(ctx, _json, encode_basestring),
238+
"encode_basestring_ascii" => named_function!(ctx, _json, encode_basestring_ascii),
218239
})
219240
}

0 commit comments

Comments
 (0)