mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 18:59:04 +08:00
Remove unnecessary string conversions in error message construction (#5826)
* Make `new_X_error` recv `impl Into<String>` * Maks the rest of the methods to be generic * Remove `.to_owned()` from more files
This commit is contained in:
1 parent
6a992d4fa2
commit
ab09de8542
18 files changed
+132
-158
No files matched your search
+13
-14
@@ -581,7 +581,7 @@ mod array {
|
||||
.chars()
|
||||
.exactly_one()
|
||||
.map(|ch| Self(ch as _))
|
||||
.map_err(|_| vm.new_type_error("array item must be unicode character".into()))
|
||||
.map_err(|_| vm.new_type_error("array item must be unicode character"))
|
||||
}
|
||||
fn byteswap(self) -> Self {
|
||||
Self(self.0.swap_bytes())
|
||||
@@ -832,9 +832,9 @@ mod array {
|
||||
))
|
||||
})?;
|
||||
if zelf.read().typecode() != 'u' {
|
||||
return Err(vm.new_value_error(
|
||||
"fromunicode() may only be called on unicode type arrays".into(),
|
||||
));
|
||||
return Err(
|
||||
vm.new_value_error("fromunicode() may only be called on unicode type arrays")
|
||||
);
|
||||
}
|
||||
let mut w = zelf.try_resizable(vm)?;
|
||||
let bytes = Self::_unicode_to_wchar_bytes(wtf8, w.itemsize());
|
||||
@@ -846,9 +846,9 @@ mod array {
|
||||
fn tounicode(&self, vm: &VirtualMachine) -> PyResult<Wtf8Buf> {
|
||||
let array = self.array.read();
|
||||
if array.typecode() != 'u' {
|
||||
return Err(vm.new_value_error(
|
||||
"tounicode() may only be called on unicode type arrays".into(),
|
||||
));
|
||||
return Err(
|
||||
vm.new_value_error("tounicode() may only be called on unicode type arrays")
|
||||
);
|
||||
}
|
||||
let bytes = array.get_bytes();
|
||||
Self::_wchar_bytes_to_string(bytes, self.itemsize(), vm)
|
||||
@@ -1500,7 +1500,7 @@ mod array {
|
||||
.unwrap_or(u8::MAX)
|
||||
.try_into()
|
||||
.map_err(|_| {
|
||||
vm.new_value_error("third argument must be a valid machine format code.".into())
|
||||
vm.new_value_error("third argument must be a valid machine format code.")
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1572,11 +1572,11 @@ mod array {
|
||||
fn check_type_code(spec: PyStrRef, vm: &VirtualMachine) -> PyResult<ArrayContentType> {
|
||||
let spec = spec.as_str().chars().exactly_one().map_err(|_| {
|
||||
vm.new_type_error(
|
||||
"_array_reconstructor() argument 2 must be a unicode character, not str".into(),
|
||||
"_array_reconstructor() argument 2 must be a unicode character, not str",
|
||||
)
|
||||
})?;
|
||||
ArrayContentType::from_char(spec)
|
||||
.map_err(|_| vm.new_value_error("second argument must be a valid type code".into()))
|
||||
.map_err(|_| vm.new_value_error("second argument must be a valid type code"))
|
||||
}
|
||||
|
||||
macro_rules! chunk_to_obj {
|
||||
@@ -1609,7 +1609,7 @@ mod array {
|
||||
let format = args.mformat_code;
|
||||
let bytes = args.items.as_bytes();
|
||||
if bytes.len() % format.item_size() != 0 {
|
||||
return Err(vm.new_value_error("bytes length not a multiple of item size".into()));
|
||||
return Err(vm.new_value_error("bytes length not a multiple of item size"));
|
||||
}
|
||||
if MachineFormatCode::from_typecode(array.typecode()) == Some(format) {
|
||||
array.frombytes(bytes);
|
||||
@@ -1642,9 +1642,8 @@ mod array {
|
||||
})?,
|
||||
MachineFormatCode::Utf16 { big_endian } => {
|
||||
let utf16: Vec<_> = chunks.map(|b| chunk_to_obj!(b, u16, big_endian)).collect();
|
||||
let s = String::from_utf16(&utf16).map_err(|_| {
|
||||
vm.new_unicode_encode_error("items cannot decode as utf16".into())
|
||||
})?;
|
||||
let s = String::from_utf16(&utf16)
|
||||
.map_err(|_| vm.new_unicode_encode_error("items cannot decode as utf16"))?;
|
||||
let bytes = PyArray::_unicode_to_wchar_bytes((*s).as_ref(), array.itemsize());
|
||||
array.frombytes_move(bytes);
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ pub mod _hashlib {
|
||||
|
||||
#[pyslot]
|
||||
fn slot_new(_cls: PyTypeRef, _args: FuncArgs, vm: &VirtualMachine) -> PyResult {
|
||||
Err(vm.new_type_error("cannot create '_hashlib.HASH' instances".into()))
|
||||
Err(vm.new_type_error("cannot create '_hashlib.HASH' instances"))
|
||||
}
|
||||
|
||||
#[pygetset]
|
||||
@@ -181,7 +181,7 @@ pub mod _hashlib {
|
||||
|
||||
#[pyslot]
|
||||
fn slot_new(_cls: PyTypeRef, _args: FuncArgs, vm: &VirtualMachine) -> PyResult {
|
||||
Err(vm.new_type_error("cannot create '_hashlib.HASHXOF' instances".into()))
|
||||
Err(vm.new_type_error("cannot create '_hashlib.HASHXOF' instances"))
|
||||
}
|
||||
|
||||
#[pygetset]
|
||||
@@ -347,7 +347,7 @@ pub mod _hashlib {
|
||||
|
||||
#[pyfunction]
|
||||
fn hmac_new(_args: NewHMACHashArgs, vm: &VirtualMachine) -> PyResult<PyObjectRef> {
|
||||
Err(vm.new_type_error("cannot create 'hmac' instances".into())) // TODO: RUSTPYTHON support hmac
|
||||
Err(vm.new_type_error("cannot create 'hmac' instances")) // TODO: RUSTPYTHON support hmac
|
||||
}
|
||||
|
||||
pub trait ThreadSafeDynDigest: DynClone + DynDigest + Sync + Send {}
|
||||
|
||||
+12
-16
@@ -497,15 +497,12 @@ fn get_int_or_index<T>(vm: &VirtualMachine, arg: PyObjectRef) -> PyResult<T>
|
||||
where
|
||||
T: PrimInt + for<'a> TryFrom<&'a BigInt>,
|
||||
{
|
||||
let index = arg.try_index_opt(vm).unwrap_or_else(|| {
|
||||
Err(new_struct_error(
|
||||
vm,
|
||||
"required argument is not an integer".to_owned(),
|
||||
))
|
||||
})?;
|
||||
let index = arg
|
||||
.try_index_opt(vm)
|
||||
.unwrap_or_else(|| Err(new_struct_error(vm, "required argument is not an integer")))?;
|
||||
index
|
||||
.try_to_primitive(vm)
|
||||
.map_err(|_| new_struct_error(vm, "argument out of range".to_owned()))
|
||||
.map_err(|_| new_struct_error(vm, "argument out of range"))
|
||||
}
|
||||
|
||||
make_pack_prim_int!(i8);
|
||||
@@ -549,7 +546,7 @@ impl Packable for f16 {
|
||||
// "from_f64 should be preferred in any non-`const` context" except it gives the wrong result :/
|
||||
let f_16 = f16::from_f64_const(f_64);
|
||||
if f_16.is_infinite() != f_64.is_infinite() {
|
||||
return Err(vm.new_overflow_error("float too large to pack with e format".to_owned()));
|
||||
return Err(vm.new_overflow_error("float too large to pack with e format"));
|
||||
}
|
||||
f_16.to_bits().pack_int::<E>(data);
|
||||
Ok(())
|
||||
@@ -586,12 +583,11 @@ impl Packable for bool {
|
||||
|
||||
fn pack_char(vm: &VirtualMachine, arg: PyObjectRef, data: &mut [u8]) -> PyResult<()> {
|
||||
let v = PyBytesRef::try_from_object(vm, arg)?;
|
||||
let ch = *v.as_bytes().iter().exactly_one().map_err(|_| {
|
||||
new_struct_error(
|
||||
vm,
|
||||
"char format requires a bytes object of length 1".to_owned(),
|
||||
)
|
||||
})?;
|
||||
let ch = *v
|
||||
.as_bytes()
|
||||
.iter()
|
||||
.exactly_one()
|
||||
.map_err(|_| new_struct_error(vm, "char format requires a bytes object of length 1"))?;
|
||||
data[0] = ch;
|
||||
Ok(())
|
||||
}
|
||||
@@ -647,8 +643,8 @@ pub fn struct_error_type(vm: &VirtualMachine) -> &'static PyTypeRef {
|
||||
INSTANCE.get_or_init(|| vm.ctx.new_exception_type("struct", "error", None))
|
||||
}
|
||||
|
||||
pub fn new_struct_error(vm: &VirtualMachine, msg: String) -> PyBaseExceptionRef {
|
||||
pub fn new_struct_error(vm: &VirtualMachine, msg: impl Into<String>) -> PyBaseExceptionRef {
|
||||
// can't just STRUCT_ERROR.get().unwrap() cause this could be called before from buffer
|
||||
// machinery, independent of whether _struct was ever imported
|
||||
vm.new_exception_msg(struct_error_type(vm).clone(), msg)
|
||||
vm.new_exception_msg(struct_error_type(vm).clone(), msg.into())
|
||||
}
|
||||
@@ -184,7 +184,7 @@ impl Representable for PyClassMethod {
|
||||
.map(|n| n.as_str()),
|
||||
class.module(vm).downcast_ref::<PyStr>().map(|m| m.as_str()),
|
||||
) {
|
||||
(None, _) => return Err(vm.new_type_error("Unknown qualified name".into())),
|
||||
(None, _) => return Err(vm.new_type_error("Unknown qualified name")),
|
||||
(Some(qualname), Some(module)) if module != "builtins" => {
|
||||
format!("<{module}.{qualname}({callable})>")
|
||||
}
|
||||
|
||||
@@ -208,7 +208,7 @@ impl Representable for PyModule {
|
||||
let module_repr = importlib.get_attr("_module_repr", vm)?;
|
||||
let repr = module_repr.call((zelf.to_owned(),), vm)?;
|
||||
repr.downcast()
|
||||
.map_err(|_| vm.new_type_error("_module_repr did not return a string".into()))
|
||||
.map_err(|_| vm.new_type_error("_module_repr did not return a string"))
|
||||
}
|
||||
|
||||
#[cold]
|
||||
|
||||
@@ -350,7 +350,7 @@ impl PyBaseObject {
|
||||
.map(|n| n.as_str()),
|
||||
class.module(vm).downcast_ref::<PyStr>().map(|m| m.as_str()),
|
||||
) {
|
||||
(None, _) => Err(vm.new_type_error("Unknown qualified name".into())),
|
||||
(None, _) => Err(vm.new_type_error("Unknown qualified name")),
|
||||
(Some(qualname), Some(module)) if module != "builtins" => Ok(PyStr::from(format!(
|
||||
"<{}.{} object at {:#x}>",
|
||||
module,
|
||||
|
||||
@@ -153,7 +153,7 @@ impl Representable for PyStaticMethod {
|
||||
.map(|n| n.as_str()),
|
||||
class.module(vm).downcast_ref::<PyStr>().map(|m| m.as_str()),
|
||||
) {
|
||||
(None, _) => Err(vm.new_type_error("Unknown qualified name".into())),
|
||||
(None, _) => Err(vm.new_type_error("Unknown qualified name")),
|
||||
(Some(qualname), Some(module)) if module != "builtins" => {
|
||||
Ok(format!("<{module}.{qualname}({callable})>"))
|
||||
}
|
||||
|
||||
@@ -428,8 +428,7 @@ impl ExceptionCtor {
|
||||
match (self, exc_inst) {
|
||||
// both are instances; which would we choose?
|
||||
(Self::Instance(_exc_a), Some(_exc_b)) => {
|
||||
Err(vm
|
||||
.new_type_error("instance exception may not have a separate value".to_owned()))
|
||||
Err(vm.new_type_error("instance exception may not have a separate value"))
|
||||
}
|
||||
// if the "type" is an instance and the value isn't, use the "type"
|
||||
(Self::Instance(exc), None) => Ok(exc),
|
||||
@@ -672,7 +671,7 @@ impl PyRef<PyBaseException> {
|
||||
if !vm.is_none(&state) {
|
||||
let dict = state
|
||||
.downcast::<crate::builtins::PyDict>()
|
||||
.map_err(|_| vm.new_type_error("state is not a dictionary".to_owned()))?;
|
||||
.map_err(|_| vm.new_type_error("state is not a dictionary"))?;
|
||||
|
||||
for (key, value) in &dict {
|
||||
let key_str = key.str(vm)?;
|
||||
@@ -691,7 +690,7 @@ impl Constructor for PyBaseException {
|
||||
|
||||
fn py_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
|
||||
if cls.is(PyBaseException::class(&vm.ctx)) && !args.kwargs.is_empty() {
|
||||
return Err(vm.new_type_error("BaseException() takes no keyword arguments".to_owned()));
|
||||
return Err(vm.new_type_error("BaseException() takes no keyword arguments"));
|
||||
}
|
||||
PyBaseException::new(args.args, vm)
|
||||
.into_ref_with_type(vm, cls)
|
||||
@@ -1149,7 +1148,7 @@ impl serde::Serialize for SerializeException<'_, '_> {
|
||||
}
|
||||
|
||||
pub fn cstring_error(vm: &VirtualMachine) -> PyBaseExceptionRef {
|
||||
vm.new_value_error("embedded null character".to_owned())
|
||||
vm.new_value_error("embedded null character")
|
||||
}
|
||||
|
||||
impl ToPyException for std::ffi::NulError {
|
||||
@@ -1353,8 +1352,7 @@ pub(super) mod types {
|
||||
// Check for any remaining invalid keyword arguments
|
||||
if let Some(invalid_key) = kwargs.keys().next() {
|
||||
return Err(vm.new_type_error(format!(
|
||||
"'{}' is an invalid keyword argument for ImportError",
|
||||
invalid_key
|
||||
"'{invalid_key}' is an invalid keyword argument for ImportError"
|
||||
)));
|
||||
}
|
||||
|
||||
|
||||
+10
-12
@@ -28,17 +28,17 @@ impl IntoPyException for FormatSpecError {
|
||||
vm.new_value_error(msg)
|
||||
}
|
||||
FormatSpecError::PrecisionNotAllowed => {
|
||||
vm.new_value_error("Precision not allowed in integer format specifier".to_owned())
|
||||
vm.new_value_error("Precision not allowed in integer format specifier")
|
||||
}
|
||||
FormatSpecError::NotAllowed(s) => {
|
||||
let msg = format!("{s} not allowed with integer format specifier 'c'");
|
||||
vm.new_value_error(msg)
|
||||
}
|
||||
FormatSpecError::UnableToConvert => {
|
||||
vm.new_value_error("Unable to convert int to float".to_owned())
|
||||
vm.new_value_error("Unable to convert int to float")
|
||||
}
|
||||
FormatSpecError::CodeNotInRange => {
|
||||
vm.new_overflow_error("%c arg not in range(0x110000)".to_owned())
|
||||
vm.new_overflow_error("%c arg not in range(0x110000)")
|
||||
}
|
||||
FormatSpecError::NotImplemented(c, s) => {
|
||||
let msg = format!("Format code '{c}' for object of type '{s}' not implemented yet");
|
||||
@@ -52,9 +52,9 @@ impl ToPyException for FormatParseError {
|
||||
fn to_pyexception(&self, vm: &VirtualMachine) -> PyBaseExceptionRef {
|
||||
match self {
|
||||
FormatParseError::UnmatchedBracket => {
|
||||
vm.new_value_error("expected '}' before end of string".to_owned())
|
||||
vm.new_value_error("expected '}' before end of string")
|
||||
}
|
||||
_ => vm.new_value_error("Unexpected error parsing format string".to_owned()),
|
||||
_ => vm.new_value_error("Unexpected error parsing format string"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -130,8 +130,7 @@ pub(crate) fn format(
|
||||
FieldType::Auto => {
|
||||
if seen_index {
|
||||
return Err(vm.new_value_error(
|
||||
"cannot switch from manual field specification to automatic field numbering"
|
||||
.to_owned(),
|
||||
"cannot switch from manual field specification to automatic field numbering",
|
||||
));
|
||||
}
|
||||
auto_argument_index += 1;
|
||||
@@ -139,13 +138,12 @@ pub(crate) fn format(
|
||||
.args
|
||||
.get(auto_argument_index - 1)
|
||||
.cloned()
|
||||
.ok_or_else(|| vm.new_index_error("tuple index out of range".to_owned()))
|
||||
.ok_or_else(|| vm.new_index_error("tuple index out of range"))
|
||||
}
|
||||
FieldType::Index(index) => {
|
||||
if auto_argument_index != 0 {
|
||||
return Err(vm.new_value_error(
|
||||
"cannot switch from automatic field numbering to manual field specification"
|
||||
.to_owned(),
|
||||
"cannot switch from automatic field numbering to manual field specification",
|
||||
));
|
||||
}
|
||||
seen_index = true;
|
||||
@@ -153,7 +151,7 @@ pub(crate) fn format(
|
||||
.args
|
||||
.get(index)
|
||||
.cloned()
|
||||
.ok_or_else(|| vm.new_index_error("tuple index out of range".to_owned()))
|
||||
.ok_or_else(|| vm.new_index_error("tuple index out of range"))
|
||||
}
|
||||
FieldType::Keyword(keyword) => keyword
|
||||
.as_str()
|
||||
@@ -170,7 +168,7 @@ pub(crate) fn format_map(
|
||||
) -> PyResult<Wtf8Buf> {
|
||||
format_internal(vm, format, &mut |field_type| match field_type {
|
||||
FieldType::Auto | FieldType::Index(_) => {
|
||||
Err(vm.new_value_error("Format string contains positional fields".to_owned()))
|
||||
Err(vm.new_value_error("Format string contains positional fields"))
|
||||
}
|
||||
FieldType::Keyword(keyword) => dict.get_item(&keyword, vm),
|
||||
})
|
||||
|
||||
+1
-1
@@ -101,7 +101,7 @@ where
|
||||
let n = vm.check_repeat_or_overflow_error(self.as_ref().len(), n)?;
|
||||
|
||||
if n > 1 && std::mem::size_of_val(self.as_ref()) >= MAX_MEMORY_SIZE / n {
|
||||
return Err(vm.new_memory_error("".to_owned()));
|
||||
return Err(vm.new_memory_error(""));
|
||||
}
|
||||
|
||||
let mut v = Vec::with_capacity(n * self.as_ref().len());
|
||||
|
||||
+1
-1
@@ -59,7 +59,7 @@ pub fn assert_in_range(signum: i32, vm: &VirtualMachine) -> PyResult<()> {
|
||||
if (1..NSIG as i32).contains(&signum) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(vm.new_value_error("signal number out of range".to_owned()))
|
||||
Err(vm.new_value_error("signal number out of range"))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+36
-51
@@ -79,14 +79,12 @@ impl TryFromObject for Fildes {
|
||||
Ok(i) => i,
|
||||
Err(obj) => {
|
||||
let fileno_meth = vm.get_attribute_opt(obj, "fileno")?.ok_or_else(|| {
|
||||
vm.new_type_error(
|
||||
"argument must be an int, or have a fileno() method.".to_owned(),
|
||||
)
|
||||
vm.new_type_error("argument must be an int, or have a fileno() method.")
|
||||
})?;
|
||||
fileno_meth
|
||||
.call((), vm)?
|
||||
.downcast()
|
||||
.map_err(|_| vm.new_type_error("fileno() returned a non-integer".to_owned()))?
|
||||
.map_err(|_| vm.new_type_error("fileno() returned a non-integer"))?
|
||||
}
|
||||
};
|
||||
let fd = int.try_to_primitive(vm)?;
|
||||
@@ -168,7 +166,7 @@ mod _io {
|
||||
|
||||
fn ensure_unclosed(file: &PyObject, msg: &str, vm: &VirtualMachine) -> PyResult<()> {
|
||||
if file.get_attr("closed", vm)?.try_to_bool(vm)? {
|
||||
Err(vm.new_value_error(msg.to_owned()))
|
||||
Err(vm.new_value_error(msg))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
@@ -226,7 +224,7 @@ mod _io {
|
||||
}
|
||||
|
||||
pub(super) fn io_closed_error(vm: &VirtualMachine) -> PyBaseExceptionRef {
|
||||
vm.new_value_error("I/O operation on closed file".to_owned())
|
||||
vm.new_value_error("I/O operation on closed file")
|
||||
}
|
||||
|
||||
#[pyattr]
|
||||
@@ -243,7 +241,7 @@ mod _io {
|
||||
}
|
||||
OptionalArg::Present(1) => SeekFrom::Current(offset.try_into_value(vm)?),
|
||||
OptionalArg::Present(2) => SeekFrom::End(offset.try_into_value(vm)?),
|
||||
_ => return Err(vm.new_value_error("invalid value for how".to_owned())),
|
||||
_ => return Err(vm.new_value_error("invalid value for how")),
|
||||
};
|
||||
Ok(seek)
|
||||
}
|
||||
@@ -694,9 +692,9 @@ mod _io {
|
||||
slice.copy_from_slice(&data);
|
||||
Ok(data.len())
|
||||
}
|
||||
None => Err(vm.new_value_error(
|
||||
"readinto: buffer and read data have different lengths".to_owned(),
|
||||
)),
|
||||
None => {
|
||||
Err(vm.new_value_error("readinto: buffer and read data have different lengths"))
|
||||
}
|
||||
}
|
||||
}
|
||||
#[pymethod]
|
||||
@@ -769,7 +767,7 @@ mod _io {
|
||||
} else {
|
||||
"I/O operation on uninitialized object"
|
||||
};
|
||||
Err(vm.new_value_error(msg.to_owned()))
|
||||
Err(vm.new_value_error(msg))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1399,7 +1397,7 @@ mod _io {
|
||||
fn lock(&self, vm: &VirtualMachine) -> PyResult<PyThreadMutexGuard<'_, BufferedData>> {
|
||||
self.data()
|
||||
.lock()
|
||||
.ok_or_else(|| vm.new_runtime_error("reentrant call inside buffered io".to_owned()))
|
||||
.ok_or_else(|| vm.new_runtime_error("reentrant call inside buffered io"))
|
||||
}
|
||||
|
||||
#[pyslot]
|
||||
@@ -1430,9 +1428,7 @@ mod _io {
|
||||
|
||||
let buffer_size = match buffer_size {
|
||||
OptionalArg::Present(i) if i <= 0 => {
|
||||
return Err(
|
||||
vm.new_value_error("buffer size must be strictly positive".to_owned())
|
||||
);
|
||||
return Err(vm.new_value_error("buffer size must be strictly positive"));
|
||||
}
|
||||
OptionalArg::Present(i) => i as usize,
|
||||
OptionalArg::Missing => DEFAULT_BUFFER_SIZE,
|
||||
@@ -1519,7 +1515,7 @@ mod _io {
|
||||
data.flags.insert(BufferedFlags::DETACHED);
|
||||
data.raw
|
||||
.take()
|
||||
.ok_or_else(|| vm.new_value_error("raw stream has been detached".to_owned()))
|
||||
.ok_or_else(|| vm.new_value_error("raw stream has been detached"))
|
||||
}
|
||||
#[pymethod]
|
||||
fn seekable(&self, vm: &VirtualMachine) -> PyResult {
|
||||
@@ -1621,7 +1617,7 @@ mod _io {
|
||||
let raw = data.check_init(vm)?;
|
||||
let n = size.size.map(|s| *s).unwrap_or(-1);
|
||||
if n < -1 {
|
||||
return Err(vm.new_value_error("read length must be non-negative or -1".to_owned()));
|
||||
return Err(vm.new_value_error("read length must be non-negative or -1"));
|
||||
}
|
||||
ensure_unclosed(raw, "read of closed file", vm)?;
|
||||
match n.to_usize() {
|
||||
@@ -2282,13 +2278,13 @@ mod _io {
|
||||
) -> PyResult<PyThreadMutexGuard<'_, Option<TextIOData>>> {
|
||||
self.data
|
||||
.lock()
|
||||
.ok_or_else(|| vm.new_runtime_error("reentrant call inside textio".to_owned()))
|
||||
.ok_or_else(|| vm.new_runtime_error("reentrant call inside textio"))
|
||||
}
|
||||
|
||||
fn lock(&self, vm: &VirtualMachine) -> PyResult<PyMappedThreadMutexGuard<'_, TextIOData>> {
|
||||
let lock = self.lock_opt(vm)?;
|
||||
PyThreadMutexGuard::try_map(lock, |x| x.as_mut())
|
||||
.map_err(|_| vm.new_value_error("I/O operation on uninitialized object".to_owned()))
|
||||
.map_err(|_| vm.new_value_error("I/O operation on uninitialized object"))
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
@@ -2421,9 +2417,7 @@ mod _io {
|
||||
let mut textio = self.lock(vm)?;
|
||||
match chunk_size {
|
||||
PySetterValue::Assign(chunk_size) => textio.chunk_size = chunk_size,
|
||||
PySetterValue::Delete => {
|
||||
Err(vm.new_attribute_error("cannot delete attribute".to_owned()))?
|
||||
}
|
||||
PySetterValue::Delete => Err(vm.new_attribute_error("cannot delete attribute"))?,
|
||||
};
|
||||
// TODO: RUSTPYTHON
|
||||
// Change chunk_size type, validate it manually and throws ValueError if invalid.
|
||||
@@ -2511,7 +2505,7 @@ mod _io {
|
||||
vm.call_method(zelf.as_object(), "flush", ())?;
|
||||
let cookie_obj = crate::builtins::PyIntRef::try_from_object(vm, cookie)?;
|
||||
let cookie = TextIOCookie::parse(cookie_obj.as_bigint())
|
||||
.ok_or_else(|| vm.new_value_error("invalid cookie".to_owned()))?;
|
||||
.ok_or_else(|| vm.new_value_error("invalid cookie"))?;
|
||||
let mut textio = zelf.lock(vm)?;
|
||||
vm.call_method(&textio.buffer, "seek", (cookie.start_pos,))?;
|
||||
textio.set_decoded_chars(None);
|
||||
@@ -2528,7 +2522,7 @@ mod _io {
|
||||
} = *textio;
|
||||
let decoder = decoder
|
||||
.as_ref()
|
||||
.ok_or_else(|| vm.new_value_error("invalid cookie".to_owned()))?;
|
||||
.ok_or_else(|| vm.new_value_error("invalid cookie"))?;
|
||||
let input_chunk = vm.call_method(buffer, "read", (cookie.bytes_to_feed,))?;
|
||||
let input_chunk: PyBytesRef = input_chunk.downcast().map_err(|obj| {
|
||||
vm.new_type_error(format!(
|
||||
@@ -2544,7 +2538,7 @@ mod _io {
|
||||
.is_code_point_boundary(cookie.bytes_to_skip as usize);
|
||||
textio.set_decoded_chars(Some(decoded));
|
||||
if !pos_is_valid {
|
||||
return Err(vm.new_os_error("can't restore logical file position".to_owned()));
|
||||
return Err(vm.new_os_error("can't restore logical file position"));
|
||||
}
|
||||
textio.decoded_chars_used = cookie.num_to_skip();
|
||||
} else {
|
||||
@@ -2567,7 +2561,7 @@ mod _io {
|
||||
));
|
||||
}
|
||||
if !textio.telling {
|
||||
return Err(vm.new_os_error("telling position disabled by next() call".to_owned()));
|
||||
return Err(vm.new_os_error("telling position disabled by next() call"));
|
||||
}
|
||||
textio.write_pending(vm)?;
|
||||
drop(textio);
|
||||
@@ -2656,9 +2650,7 @@ mod _io {
|
||||
let final_decoded_chars = n_decoded.chars + decoded.char_len();
|
||||
cookie.need_eof = true;
|
||||
if final_decoded_chars < num_to_skip.chars {
|
||||
return Err(
|
||||
vm.new_os_error("can't reconstruct logical file position".to_owned())
|
||||
);
|
||||
return Err(vm.new_os_error("can't reconstruct logical file position"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3010,7 +3002,7 @@ mod _io {
|
||||
|
||||
fn parse_decoder_state(state: PyObjectRef, vm: &VirtualMachine) -> PyResult<(PyBytesRef, i32)> {
|
||||
use crate::builtins::{PyTuple, int};
|
||||
let state_err = || vm.new_type_error("illegal decoder state".to_owned());
|
||||
let state_err = || vm.new_type_error("illegal decoder state");
|
||||
let state = state.downcast::<PyTuple>().map_err(|_| state_err())?;
|
||||
match state.as_slice() {
|
||||
[buf, flags] => {
|
||||
@@ -3220,7 +3212,7 @@ mod _io {
|
||||
) -> PyResult<PyThreadMutexGuard<'_, Option<IncrementalNewlineDecoderData>>> {
|
||||
self.data
|
||||
.lock()
|
||||
.ok_or_else(|| vm.new_runtime_error("reentrant call inside nldecoder".to_owned()))
|
||||
.ok_or_else(|| vm.new_runtime_error("reentrant call inside nldecoder"))
|
||||
}
|
||||
|
||||
fn lock(
|
||||
@@ -3228,9 +3220,8 @@ mod _io {
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<PyMappedThreadMutexGuard<'_, IncrementalNewlineDecoderData>> {
|
||||
let lock = self.lock_opt(vm)?;
|
||||
PyThreadMutexGuard::try_map(lock, |x| x.as_mut()).map_err(|_| {
|
||||
vm.new_value_error("I/O operation on uninitialized nldecoder".to_owned())
|
||||
})
|
||||
PyThreadMutexGuard::try_map(lock, |x| x.as_mut())
|
||||
.map_err(|_| vm.new_value_error("I/O operation on uninitialized nldecoder"))
|
||||
}
|
||||
|
||||
#[pymethod]
|
||||
@@ -3466,15 +3457,14 @@ mod _io {
|
||||
let bytes = data.as_bytes();
|
||||
self.buffer(vm)?
|
||||
.write(bytes)
|
||||
.ok_or_else(|| vm.new_type_error("Error Writing String".to_owned()))
|
||||
.ok_or_else(|| vm.new_type_error("Error Writing String"))
|
||||
}
|
||||
|
||||
// return the entire contents of the underlying
|
||||
#[pymethod]
|
||||
fn getvalue(&self, vm: &VirtualMachine) -> PyResult<Wtf8Buf> {
|
||||
let bytes = self.buffer(vm)?.getvalue();
|
||||
Wtf8Buf::from_bytes(bytes)
|
||||
.map_err(|_| vm.new_value_error("Error Retrieving Value".to_owned()))
|
||||
Wtf8Buf::from_bytes(bytes).map_err(|_| vm.new_value_error("Error Retrieving Value"))
|
||||
}
|
||||
|
||||
// skip to the jth position
|
||||
@@ -3498,7 +3488,7 @@ mod _io {
|
||||
let data = self.buffer(vm)?.read(size.to_usize()).unwrap_or_default();
|
||||
|
||||
let value = Wtf8Buf::from_bytes(data)
|
||||
.map_err(|_| vm.new_value_error("Error Retrieving Value".to_owned()))?;
|
||||
.map_err(|_| vm.new_value_error("Error Retrieving Value"))?;
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
@@ -3512,8 +3502,7 @@ mod _io {
|
||||
// TODO size should correspond to the number of characters, at the moments its the number of
|
||||
// bytes.
|
||||
let input = self.buffer(vm)?.readline(size.to_usize(), vm)?;
|
||||
Wtf8Buf::from_bytes(input)
|
||||
.map_err(|_| vm.new_value_error("Error Retrieving Value".to_owned()))
|
||||
Wtf8Buf::from_bytes(input).map_err(|_| vm.new_value_error("Error Retrieving Value"))
|
||||
}
|
||||
|
||||
#[pymethod]
|
||||
@@ -3580,7 +3569,7 @@ mod _io {
|
||||
fn write(&self, data: ArgBytesLike, vm: &VirtualMachine) -> PyResult<u64> {
|
||||
let mut buffer = self.try_resizable(vm)?;
|
||||
data.with_ref(|b| buffer.write(b))
|
||||
.ok_or_else(|| vm.new_type_error("Error Writing Bytes".to_owned()))
|
||||
.ok_or_else(|| vm.new_type_error("Error Writing Bytes"))
|
||||
}
|
||||
|
||||
// Retrieves the entire bytes object value from the underlying buffer
|
||||
@@ -3606,7 +3595,7 @@ mod _io {
|
||||
let ret = buf
|
||||
.cursor
|
||||
.read(&mut obj.borrow_buf_mut())
|
||||
.map_err(|_| vm.new_value_error("Error readinto from Take".to_owned()))?;
|
||||
.map_err(|_| vm.new_value_error("Error readinto from Take"))?;
|
||||
|
||||
Ok(ret)
|
||||
}
|
||||
@@ -3874,7 +3863,7 @@ mod _io {
|
||||
None
|
||||
};
|
||||
if let Some(msg) = msg {
|
||||
return Err(vm.new_value_error(msg.to_owned()));
|
||||
return Err(vm.new_value_error(msg));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3923,9 +3912,7 @@ mod _io {
|
||||
|
||||
if buffering == 0 {
|
||||
let ret = match mode.encode {
|
||||
EncodeMode::Text => {
|
||||
Err(vm.new_value_error("can't have unbuffered text I/O".to_owned()))
|
||||
}
|
||||
EncodeMode::Text => Err(vm.new_value_error("can't have unbuffered text I/O")),
|
||||
EncodeMode::Bytes => Ok(raw),
|
||||
};
|
||||
return ret;
|
||||
@@ -4196,7 +4183,7 @@ mod fileio {
|
||||
let arg_fd = if let Some(i) = name.payload::<crate::builtins::PyInt>() {
|
||||
let fd = i.try_to_primitive(vm)?;
|
||||
if fd < 0 {
|
||||
return Err(vm.new_value_error("negative file descriptor".to_owned()));
|
||||
return Err(vm.new_value_error("negative file descriptor"));
|
||||
}
|
||||
Some(fd)
|
||||
} else {
|
||||
@@ -4217,15 +4204,13 @@ mod fileio {
|
||||
} else {
|
||||
zelf.closefd.store(true);
|
||||
if !args.closefd {
|
||||
return Err(
|
||||
vm.new_value_error("Cannot use closefd=False with file name".to_owned())
|
||||
);
|
||||
return Err(vm.new_value_error("Cannot use closefd=False with file name"));
|
||||
}
|
||||
|
||||
if let Some(opener) = args.opener {
|
||||
let fd = opener.call((name.clone(), flags), vm)?;
|
||||
if !fd.fast_isinstance(vm.ctx.types.int_type) {
|
||||
return Err(vm.new_type_error("expected integer from opener".to_owned()));
|
||||
return Err(vm.new_type_error("expected integer from opener"));
|
||||
}
|
||||
let fd = i32::try_from_object(vm, fd)?;
|
||||
if fd < 0 {
|
||||
|
||||
+12
-8
@@ -22,9 +22,10 @@ macro_rules! define_exception_fn {
|
||||
stringify!($python_repr),
|
||||
" object.\nUseful for raising errors from python functions implemented in rust."
|
||||
)]
|
||||
pub fn $fn_name(&self, msg: String) -> PyBaseExceptionRef {
|
||||
pub fn $fn_name(&self, msg: impl Into<String>) -> PyBaseExceptionRef
|
||||
{
|
||||
let err = self.ctx.exceptions.$attr.to_owned();
|
||||
self.new_exception_msg(err, msg)
|
||||
self.new_exception_msg(err, msg.into())
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -155,9 +156,9 @@ impl VirtualMachine {
|
||||
attribute_error
|
||||
}
|
||||
|
||||
pub fn new_name_error(&self, msg: String, name: PyStrRef) -> PyBaseExceptionRef {
|
||||
pub fn new_name_error(&self, msg: impl Into<String>, name: PyStrRef) -> PyBaseExceptionRef {
|
||||
let name_error_type = self.ctx.exceptions.name_error.to_owned();
|
||||
let name_error = self.new_exception_msg(name_error_type, msg);
|
||||
let name_error = self.new_exception_msg(name_error_type, msg.into());
|
||||
name_error.as_object().set_attr("name", name, self).unwrap();
|
||||
name_error
|
||||
}
|
||||
@@ -200,13 +201,16 @@ impl VirtualMachine {
|
||||
))
|
||||
}
|
||||
|
||||
pub fn new_errno_error(&self, errno: i32, msg: String) -> PyBaseExceptionRef {
|
||||
pub fn new_errno_error(&self, errno: i32, msg: impl Into<String>) -> PyBaseExceptionRef {
|
||||
let vm = self;
|
||||
let exc_type =
|
||||
crate::exceptions::errno_to_exc_type(errno, vm).unwrap_or(vm.ctx.exceptions.os_error);
|
||||
|
||||
let errno_obj = vm.new_pyobj(errno);
|
||||
vm.new_exception(exc_type.to_owned(), vec![errno_obj, vm.new_pyobj(msg)])
|
||||
vm.new_exception(
|
||||
exc_type.to_owned(),
|
||||
vec![errno_obj, vm.new_pyobj(msg.into())],
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_unicode_decode_error_real(
|
||||
@@ -466,9 +470,9 @@ impl VirtualMachine {
|
||||
self.new_syntax_error_maybe_incomplete(error, source, false)
|
||||
}
|
||||
|
||||
pub fn new_import_error(&self, msg: String, name: PyStrRef) -> PyBaseExceptionRef {
|
||||
pub fn new_import_error(&self, msg: impl Into<String>, name: PyStrRef) -> PyBaseExceptionRef {
|
||||
let import_error = self.ctx.exceptions.import_error.to_owned();
|
||||
let exc = self.new_exception_msg(import_error, msg);
|
||||
let exc = self.new_exception_msg(import_error, msg.into());
|
||||
exc.as_object().set_attr("name", name, self).unwrap();
|
||||
exc
|
||||
}
|
||||
|
||||
+22
-18
@@ -128,7 +128,7 @@ impl VirtualMachine {
|
||||
})?
|
||||
.try_to_primitive::<isize>(self)?;
|
||||
if hint.is_negative() {
|
||||
Err(self.new_value_error("__length_hint__() should return >= 0".to_owned()))
|
||||
Err(self.new_value_error("__length_hint__() should return >= 0"))
|
||||
} else {
|
||||
Ok(Some(hint as usize))
|
||||
}
|
||||
@@ -142,7 +142,7 @@ impl VirtualMachine {
|
||||
} else {
|
||||
let n = n as usize;
|
||||
if length > crate::stdlib::sys::MAXSIZE as usize / n {
|
||||
Err(self.new_overflow_error("repeated value are too long".to_owned()))
|
||||
Err(self.new_overflow_error("repeated value are too long"))
|
||||
} else {
|
||||
Ok(n)
|
||||
}
|
||||
@@ -407,16 +407,18 @@ impl VirtualMachine {
|
||||
return Ok(result);
|
||||
}
|
||||
if let Ok(seq_a) = PySequence::try_protocol(a, self) {
|
||||
let n =
|
||||
b.try_index(self)?.as_bigint().to_isize().ok_or_else(|| {
|
||||
self.new_overflow_error("repeated bytes are too long".to_owned())
|
||||
})?;
|
||||
let n = b
|
||||
.try_index(self)?
|
||||
.as_bigint()
|
||||
.to_isize()
|
||||
.ok_or_else(|| self.new_overflow_error("repeated bytes are too long"))?;
|
||||
return seq_a.repeat(n, self);
|
||||
} else if let Ok(seq_b) = PySequence::try_protocol(b, self) {
|
||||
let n =
|
||||
a.try_index(self)?.as_bigint().to_isize().ok_or_else(|| {
|
||||
self.new_overflow_error("repeated bytes are too long".to_owned())
|
||||
})?;
|
||||
let n = a
|
||||
.try_index(self)?
|
||||
.as_bigint()
|
||||
.to_isize()
|
||||
.ok_or_else(|| self.new_overflow_error("repeated bytes are too long"))?;
|
||||
return seq_b.repeat(n, self);
|
||||
}
|
||||
Err(self.new_unsupported_bin_op_error(a, b, "*"))
|
||||
@@ -433,16 +435,18 @@ impl VirtualMachine {
|
||||
return Ok(result);
|
||||
}
|
||||
if let Ok(seq_a) = PySequence::try_protocol(a, self) {
|
||||
let n =
|
||||
b.try_index(self)?.as_bigint().to_isize().ok_or_else(|| {
|
||||
self.new_overflow_error("repeated bytes are too long".to_owned())
|
||||
})?;
|
||||
let n = b
|
||||
.try_index(self)?
|
||||
.as_bigint()
|
||||
.to_isize()
|
||||
.ok_or_else(|| self.new_overflow_error("repeated bytes are too long"))?;
|
||||
return seq_a.inplace_repeat(n, self);
|
||||
} else if let Ok(seq_b) = PySequence::try_protocol(b, self) {
|
||||
let n =
|
||||
a.try_index(self)?.as_bigint().to_isize().ok_or_else(|| {
|
||||
self.new_overflow_error("repeated bytes are too long".to_owned())
|
||||
})?;
|
||||
let n = a
|
||||
.try_index(self)?
|
||||
.as_bigint()
|
||||
.to_isize()
|
||||
.ok_or_else(|| self.new_overflow_error("repeated bytes are too long"))?;
|
||||
/* Note that the right hand operand should not be
|
||||
* mutated in this case so inplace_repeat is not
|
||||
* used. */
|
||||
|
||||
+5
-7
@@ -109,7 +109,7 @@ fn get_filter(
|
||||
|
||||
let filters: PyListRef = filters
|
||||
.try_into_value(vm)
|
||||
.map_err(|_| vm.new_value_error("_warnings.filters must be a list".to_string()))?;
|
||||
.map_err(|_| vm.new_value_error("_warnings.filters must be a list"))?;
|
||||
|
||||
/* WarningsState.filters could change while we are iterating over it. */
|
||||
for i in 0..filters.borrow_vec().len() {
|
||||
@@ -125,7 +125,7 @@ fn get_filter(
|
||||
let action = if let Some(action) = tmp_item.first() {
|
||||
action.str(vm).map(|action| action.into_object())
|
||||
} else {
|
||||
Err(vm.new_type_error("action must be a string".to_string()))
|
||||
Err(vm.new_type_error("action must be a string"))
|
||||
};
|
||||
|
||||
let good_msg = if let Some(msg) = tmp_item.get(1) {
|
||||
@@ -224,7 +224,7 @@ fn warn_explicit(
|
||||
) -> PyResult<()> {
|
||||
let registry: PyObjectRef = registry
|
||||
.try_into_value(vm)
|
||||
.map_err(|_| vm.new_type_error("'registry' must be a dict or None".to_owned()))?;
|
||||
.map_err(|_| vm.new_type_error("'registry' must be a dict or None"))?;
|
||||
|
||||
// Normalize module.
|
||||
let module = match module.or_else(|| normalize_module(&filename, vm)) {
|
||||
@@ -314,13 +314,11 @@ fn call_show_warning(
|
||||
return show_warning(filename, lineno, message, category, source_line, vm);
|
||||
};
|
||||
if !show_fn.is_callable() {
|
||||
return Err(
|
||||
vm.new_type_error("warnings._showwarnmsg() must be set to a callable".to_owned())
|
||||
);
|
||||
return Err(vm.new_type_error("warnings._showwarnmsg() must be set to a callable"));
|
||||
}
|
||||
let Some(warnmsg_cls) = get_warnings_attr(vm, identifier!(&vm.ctx, WarningMessage), false)?
|
||||
else {
|
||||
return Err(vm.new_type_error("unable to get warnings.WarningMessage".to_owned()));
|
||||
return Err(vm.new_type_error("unable to get warnings.WarningMessage"));
|
||||
};
|
||||
|
||||
let msg = warnmsg_cls.call(
|
||||
|
||||
@@ -29,7 +29,7 @@ mod _browser {
|
||||
"json" => Ok(FetchResponseFormat::Json),
|
||||
"text" => Ok(FetchResponseFormat::Text),
|
||||
"array_buffer" => Ok(FetchResponseFormat::ArrayBuffer),
|
||||
_ => Err(vm.new_type_error("Unknown fetch response_format".into())),
|
||||
_ => Err(vm.new_type_error("Unknown fetch response_format")),
|
||||
}
|
||||
}
|
||||
fn get_response(&self, response: &web_sys::Response) -> Result<Promise, JsValue> {
|
||||
|
||||
@@ -57,7 +57,7 @@ pub fn py_err_to_js_err(vm: &VirtualMachine, py_err: &PyBaseExceptionRef) -> JsV
|
||||
|
||||
pub fn js_py_typeerror(vm: &VirtualMachine, js_err: JsValue) -> PyBaseExceptionRef {
|
||||
let msg = js_err.unchecked_into::<js_sys::Error>().to_string();
|
||||
vm.new_type_error(msg.into())
|
||||
vm.new_type_error(msg)
|
||||
}
|
||||
|
||||
pub fn js_err_to_py_err(vm: &VirtualMachine, js_err: &JsValue) -> PyBaseExceptionRef {
|
||||
|
||||
@@ -136,9 +136,7 @@ mod _js {
|
||||
} else if proto.value.is_null() {
|
||||
Object::create(proto.value.unchecked_ref())
|
||||
} else {
|
||||
return Err(
|
||||
vm.new_value_error("prototype must be an Object or null".to_owned())
|
||||
);
|
||||
return Err(vm.new_value_error("prototype must be an Object or null"));
|
||||
}
|
||||
} else {
|
||||
Object::new()
|
||||
@@ -184,7 +182,7 @@ mod _js {
|
||||
let func = self
|
||||
.value
|
||||
.dyn_ref::<js_sys::Function>()
|
||||
.ok_or_else(|| vm.new_type_error("JS value is not callable".to_owned()))?;
|
||||
.ok_or_else(|| vm.new_type_error("JS value is not callable"))?;
|
||||
let js_args = args.iter().map(|x| -> &PyJsValue { x }).collect::<Array>();
|
||||
let res = match opts.this {
|
||||
Some(this) => Reflect::apply(func, &this.value, &js_args),
|
||||
@@ -216,7 +214,7 @@ mod _js {
|
||||
let ctor = self
|
||||
.value
|
||||
.dyn_ref::<js_sys::Function>()
|
||||
.ok_or_else(|| vm.new_type_error("JS value is not callable".to_owned()))?;
|
||||
.ok_or_else(|| vm.new_type_error("JS value is not callable"))?;
|
||||
let proto = opts
|
||||
.prototype
|
||||
.as_ref()
|
||||
@@ -361,9 +359,7 @@ mod _js {
|
||||
#[pymethod]
|
||||
fn destroy(&self, vm: &VirtualMachine) -> PyResult<()> {
|
||||
let (closure, _) = self.closure.replace(None).ok_or_else(|| {
|
||||
vm.new_value_error(
|
||||
"can't destroy closure has already been destroyed or detached".to_owned(),
|
||||
)
|
||||
vm.new_value_error("can't destroy closure has already been destroyed or detached")
|
||||
})?;
|
||||
drop(closure);
|
||||
self.destroyed.set(true);
|
||||
@@ -372,9 +368,7 @@ mod _js {
|
||||
#[pymethod]
|
||||
fn detach(&self, vm: &VirtualMachine) -> PyResult<PyJsValueRef> {
|
||||
let (closure, js_val) = self.closure.replace(None).ok_or_else(|| {
|
||||
vm.new_value_error(
|
||||
"can't detach closure has already been detached or destroyed".to_owned(),
|
||||
)
|
||||
vm.new_value_error("can't detach closure has already been detached or destroyed")
|
||||
})?;
|
||||
closure.forget();
|
||||
self.detached.set(true);
|
||||
@@ -574,9 +568,7 @@ mod _js {
|
||||
match self.obj.take() {
|
||||
Some(prom) => {
|
||||
if val.is_some() {
|
||||
Err(vm.new_type_error(
|
||||
"can't send non-None value to an AwaitPromise".to_owned(),
|
||||
))
|
||||
Err(vm.new_type_error("can't send non-None value to an AwaitPromise"))
|
||||
} else {
|
||||
Ok(PyIterReturn::Return(prom))
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user