mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 18:59:04 +08:00
Implement more warnings
This commit is contained in:
1 parent
7271f56c28
commit
654a3eab8c
13 files changed
+570
-220
No files matched your search
@@ -1,8 +1,10 @@
|
||||
abiflags
|
||||
abstractmethods
|
||||
addcompare
|
||||
aenter
|
||||
aexit
|
||||
aiter
|
||||
altzone
|
||||
anext
|
||||
anextawaitable
|
||||
annotationlib
|
||||
@@ -24,6 +26,7 @@ breakpointhook
|
||||
cformat
|
||||
chunksize
|
||||
classcell
|
||||
classmethods
|
||||
closefd
|
||||
closesocket
|
||||
codepoint
|
||||
@@ -32,6 +35,8 @@ codesize
|
||||
contextvar
|
||||
cpython
|
||||
cratio
|
||||
ctype
|
||||
ctypes
|
||||
dealloc
|
||||
debugbuild
|
||||
decompressor
|
||||
@@ -74,6 +79,8 @@ fstring
|
||||
fstrings
|
||||
ftruncate
|
||||
genexpr
|
||||
genexpressions
|
||||
getargs
|
||||
getattro
|
||||
getcodesize
|
||||
getdefaultencoding
|
||||
@@ -83,14 +90,17 @@ getformat
|
||||
getframe
|
||||
getframemodulename
|
||||
getnewargs
|
||||
getopt
|
||||
getpip
|
||||
getrandom
|
||||
getrecursionlimit
|
||||
getrefcount
|
||||
getsizeof
|
||||
getswitchinterval
|
||||
getweakref
|
||||
getweakrefcount
|
||||
getweakrefs
|
||||
getweakrefs
|
||||
getwindowsversion
|
||||
gmtoff
|
||||
groupdict
|
||||
@@ -103,8 +113,12 @@ idxs
|
||||
impls
|
||||
indexgroup
|
||||
infj
|
||||
inittab
|
||||
Inittab
|
||||
instancecheck
|
||||
instanceof
|
||||
interpchannels
|
||||
interpqueues
|
||||
irepeat
|
||||
isabstractmethod
|
||||
isbytes
|
||||
@@ -129,6 +143,7 @@ listcomp
|
||||
longrange
|
||||
lvalue
|
||||
mappingproxy
|
||||
markupbase
|
||||
maskpri
|
||||
maxdigits
|
||||
MAXGROUPS
|
||||
@@ -144,6 +159,7 @@ mformat
|
||||
mro
|
||||
mros
|
||||
multiarch
|
||||
mymodule
|
||||
namereplace
|
||||
nanj
|
||||
nbytes
|
||||
@@ -156,6 +172,7 @@ nlocals
|
||||
NOARGS
|
||||
nonbytes
|
||||
Nonprintable
|
||||
onceregistry
|
||||
origname
|
||||
ospath
|
||||
pendingcr
|
||||
@@ -170,7 +187,10 @@ profilefunc
|
||||
pycache
|
||||
pycodecs
|
||||
pycs
|
||||
pydatetime
|
||||
pyexpat
|
||||
pyio
|
||||
pymain
|
||||
PYTHONAPI
|
||||
PYTHONBREAKPOINT
|
||||
PYTHONDEBUG
|
||||
@@ -220,10 +240,13 @@ scproxy
|
||||
seennl
|
||||
setattro
|
||||
setcomp
|
||||
setprofileallthreads
|
||||
setrecursionlimit
|
||||
setswitchinterval
|
||||
settraceallthreads
|
||||
showwarnmsg
|
||||
signum
|
||||
sitebuiltins
|
||||
slotnames
|
||||
STACKLESS
|
||||
stacklevel
|
||||
@@ -232,14 +255,17 @@ startpos
|
||||
subclassable
|
||||
subclasscheck
|
||||
subclasshook
|
||||
subclassing
|
||||
suboffset
|
||||
suboffsets
|
||||
SUBPATTERN
|
||||
subpatterns
|
||||
sumprod
|
||||
surrogateescape
|
||||
surrogatepass
|
||||
sysconf
|
||||
sysconfigdata
|
||||
sysdict
|
||||
sysvars
|
||||
teedata
|
||||
thisclass
|
||||
@@ -266,35 +292,10 @@ warnopts
|
||||
weaklist
|
||||
weakproxy
|
||||
weakrefs
|
||||
weakrefset
|
||||
winver
|
||||
withdata
|
||||
xmlcharrefreplace
|
||||
xoptions
|
||||
xopts
|
||||
yieldfrom
|
||||
addcompare
|
||||
altzone
|
||||
classmethods
|
||||
ctype
|
||||
ctypes
|
||||
genexpressions
|
||||
getargs
|
||||
getopt
|
||||
getweakref
|
||||
getweakrefs
|
||||
inittab
|
||||
Inittab
|
||||
interpchannels
|
||||
interpqueues
|
||||
markupbase
|
||||
mymodule
|
||||
pydatetime
|
||||
pyio
|
||||
pymain
|
||||
setprofileallthreads
|
||||
settraceallthreads
|
||||
sitebuiltins
|
||||
subclassing
|
||||
subpatterns
|
||||
sysdict
|
||||
weakrefset
|
||||
Vendored
-1
@@ -591,7 +591,6 @@ class ImportSideEffectTests(unittest.TestCase):
|
||||
|
||||
class StartupImportTests(unittest.TestCase):
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
@support.requires_subprocess()
|
||||
def test_startup_imports(self):
|
||||
# Get sys.path in isolated mode (python3 -I)
|
||||
|
||||
Vendored
-26
@@ -807,7 +807,6 @@ class CWarnTests(WarnTests, unittest.TestCase):
|
||||
|
||||
# As an early adopter, we sanity check the
|
||||
# test.import_helper.import_fresh_module utility function
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: 'function' object has unexpected attribute '__code__'
|
||||
def test_accelerated(self):
|
||||
self.assertIsNot(original_warnings, self.module)
|
||||
self.assertNotHasAttr(self.module.warn, '__code__')
|
||||
@@ -1012,7 +1011,6 @@ class _WarningsTests(BaseTest, unittest.TestCase):
|
||||
result = stream.getvalue()
|
||||
self.assertIn(text, result)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'warnings' has no attribute '_showwarnmsg'. Did you mean: 'showwarning'?
|
||||
def test_showwarnmsg_missing(self):
|
||||
# Test that _showwarnmsg() missing is okay.
|
||||
text = 'del _showwarnmsg test'
|
||||
@@ -1458,7 +1456,6 @@ class PyCatchWarningTests(CatchWarningTests, unittest.TestCase):
|
||||
|
||||
class EnvironmentVariableTests(BaseTest):
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: b'[]' != b"['ignore::DeprecationWarning']"
|
||||
def test_single_warning(self):
|
||||
rc, stdout, stderr = assert_python_ok("-c",
|
||||
"import sys; sys.stdout.write(str(sys.warnoptions))",
|
||||
@@ -1466,7 +1463,6 @@ class EnvironmentVariableTests(BaseTest):
|
||||
PYTHONDEVMODE="")
|
||||
self.assertEqual(stdout, b"['ignore::DeprecationWarning']")
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: b'[]' != b"['ignore::DeprecationWarning', 'ignore::UnicodeWarning']"
|
||||
def test_comma_separated_warnings(self):
|
||||
rc, stdout, stderr = assert_python_ok("-c",
|
||||
"import sys; sys.stdout.write(str(sys.warnoptions))",
|
||||
@@ -1475,7 +1471,6 @@ class EnvironmentVariableTests(BaseTest):
|
||||
self.assertEqual(stdout,
|
||||
b"['ignore::DeprecationWarning', 'ignore::UnicodeWarning']")
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: b"['ignore::UnicodeWarning']" != b"['ignore::DeprecationWarning', 'ignore::UnicodeWarning']"
|
||||
@force_not_colorized
|
||||
def test_envvar_and_command_line(self):
|
||||
rc, stdout, stderr = assert_python_ok("-Wignore::UnicodeWarning", "-c",
|
||||
@@ -1535,7 +1530,6 @@ class EnvironmentVariableTests(BaseTest):
|
||||
self.assertEqual(stdout_lines, expected_output)
|
||||
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: b'[]' != b"['ignore:DeprecationWarning\xc3\xa6']"
|
||||
@unittest.skipUnless(sys.getfilesystemencoding() != 'ascii',
|
||||
'requires non-ascii filesystemencoding')
|
||||
def test_nonascii(self):
|
||||
@@ -1550,10 +1544,6 @@ class EnvironmentVariableTests(BaseTest):
|
||||
class CEnvironmentVariableTests(EnvironmentVariableTests, unittest.TestCase):
|
||||
module = c_warnings
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; Lists differ
|
||||
def test_default_filter_configuration(self):
|
||||
return super().test_default_filter_configuration()
|
||||
|
||||
class PyEnvironmentVariableTests(EnvironmentVariableTests, unittest.TestCase):
|
||||
module = py_warnings
|
||||
|
||||
@@ -1851,7 +1841,6 @@ class DeprecatedTests(PyPublicAPITests):
|
||||
self.assertEqual(len(overloads), 2)
|
||||
self.assertEqual(overloads[0].__deprecated__, "no more ints")
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; DeprecationWarning not triggered
|
||||
def test_class(self):
|
||||
@deprecated("A will go away soon")
|
||||
class A:
|
||||
@@ -1863,7 +1852,6 @@ class DeprecatedTests(PyPublicAPITests):
|
||||
with self.assertRaises(TypeError):
|
||||
A(42)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; DeprecationWarning not triggered
|
||||
def test_class_with_init(self):
|
||||
@deprecated("HasInit will go away soon")
|
||||
class HasInit:
|
||||
@@ -1874,7 +1862,6 @@ class DeprecatedTests(PyPublicAPITests):
|
||||
instance = HasInit(42)
|
||||
self.assertEqual(instance.x, 42)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; DeprecationWarning not triggered
|
||||
def test_class_with_new(self):
|
||||
has_new_called = False
|
||||
|
||||
@@ -1893,7 +1880,6 @@ class DeprecatedTests(PyPublicAPITests):
|
||||
self.assertEqual(instance.x, 42)
|
||||
self.assertTrue(has_new_called)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; DeprecationWarning not triggered
|
||||
def test_class_with_inherited_new(self):
|
||||
new_base_called = False
|
||||
|
||||
@@ -1915,7 +1901,6 @@ class DeprecatedTests(PyPublicAPITests):
|
||||
self.assertEqual(instance.x, 42)
|
||||
self.assertTrue(new_base_called)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; DeprecationWarning not triggered
|
||||
def test_class_with_new_but_no_init(self):
|
||||
new_called = False
|
||||
|
||||
@@ -1933,7 +1918,6 @@ class DeprecatedTests(PyPublicAPITests):
|
||||
self.assertEqual(instance.x, 42)
|
||||
self.assertTrue(new_called)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; DeprecationWarning not triggered
|
||||
def test_mixin_class(self):
|
||||
@deprecated("Mixin will go away soon")
|
||||
class Mixin:
|
||||
@@ -1950,7 +1934,6 @@ class DeprecatedTests(PyPublicAPITests):
|
||||
instance = Child(42)
|
||||
self.assertEqual(instance.a, 42)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; DeprecationWarning not triggered
|
||||
def test_do_not_shadow_user_arguments(self):
|
||||
new_called = False
|
||||
new_called_cls = None
|
||||
@@ -1970,7 +1953,6 @@ class DeprecatedTests(PyPublicAPITests):
|
||||
self.assertTrue(new_called)
|
||||
self.assertEqual(new_called_cls, 'haha')
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; DeprecationWarning not triggered
|
||||
def test_existing_init_subclass(self):
|
||||
@deprecated("C will go away soon")
|
||||
class C:
|
||||
@@ -1987,7 +1969,6 @@ class DeprecatedTests(PyPublicAPITests):
|
||||
self.assertTrue(D.inited)
|
||||
self.assertIsInstance(D(), D) # no deprecation
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; DeprecationWarning not triggered
|
||||
def test_existing_init_subclass_in_base(self):
|
||||
class Base:
|
||||
def __init_subclass__(cls, x) -> None:
|
||||
@@ -2008,7 +1989,6 @@ class DeprecatedTests(PyPublicAPITests):
|
||||
|
||||
self.assertEqual(D.inited, 3)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; DeprecationWarning not triggered
|
||||
def test_existing_init_subclass_in_sibling_base(self):
|
||||
@deprecated("A will go away soon")
|
||||
class A:
|
||||
@@ -2028,7 +2008,6 @@ class DeprecatedTests(PyPublicAPITests):
|
||||
pass
|
||||
self.assertEqual(D.inited, 42)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; DeprecationWarning not triggered
|
||||
def test_init_subclass_has_correct_cls(self):
|
||||
init_subclass_saw = None
|
||||
|
||||
@@ -2046,7 +2025,6 @@ class DeprecatedTests(PyPublicAPITests):
|
||||
|
||||
self.assertIs(init_subclass_saw, C)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; DeprecationWarning not triggered
|
||||
def test_init_subclass_with_explicit_classmethod(self):
|
||||
init_subclass_saw = None
|
||||
|
||||
@@ -2065,7 +2043,6 @@ class DeprecatedTests(PyPublicAPITests):
|
||||
|
||||
self.assertIs(init_subclass_saw, C)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; DeprecationWarning not triggered
|
||||
def test_function(self):
|
||||
@deprecated("b will go away soon")
|
||||
def b():
|
||||
@@ -2074,7 +2051,6 @@ class DeprecatedTests(PyPublicAPITests):
|
||||
with self.assertWarnsRegex(DeprecationWarning, "b will go away soon"):
|
||||
b()
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; DeprecationWarning not triggered
|
||||
def test_method(self):
|
||||
class Capybara:
|
||||
@deprecated("x will go away soon")
|
||||
@@ -2085,7 +2061,6 @@ class DeprecatedTests(PyPublicAPITests):
|
||||
with self.assertWarnsRegex(DeprecationWarning, "x will go away soon"):
|
||||
instance.x()
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; DeprecationWarning not triggered
|
||||
def test_property(self):
|
||||
class Capybara:
|
||||
@property
|
||||
@@ -2113,7 +2088,6 @@ class DeprecatedTests(PyPublicAPITests):
|
||||
with self.assertWarnsRegex(DeprecationWarning, "no more setting"):
|
||||
instance.no_more_setting = 42
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; RuntimeWarning not triggered
|
||||
def test_category(self):
|
||||
@deprecated("c will go away soon", category=RuntimeWarning)
|
||||
def c():
|
||||
|
||||
@@ -1014,10 +1014,12 @@ pub(crate) mod _asyncio {
|
||||
// Warn about deprecated (type, val, tb) signature
|
||||
if exc_val.is_present() || exc_tb.is_present() {
|
||||
warn::warn(
|
||||
vm.ctx.new_str(
|
||||
"the (type, val, tb) signature of throw() is deprecated, \
|
||||
vm.ctx
|
||||
.new_str(
|
||||
"the (type, val, tb) signature of throw() is deprecated, \
|
||||
use throw(val) instead",
|
||||
),
|
||||
)
|
||||
.into(),
|
||||
Some(vm.ctx.exceptions.deprecation_warning.to_owned()),
|
||||
1,
|
||||
None,
|
||||
|
||||
@@ -2144,7 +2144,7 @@ mod _socket {
|
||||
laddr
|
||||
);
|
||||
let _ = crate::vm::warn::warn(
|
||||
vm.ctx.new_str(msg),
|
||||
vm.ctx.new_str(msg).into(),
|
||||
Some(vm.ctx.exceptions.resource_warning.to_owned()),
|
||||
1,
|
||||
None,
|
||||
|
||||
@@ -300,10 +300,12 @@ pub fn warn_deprecated_throw_signature(
|
||||
) -> PyResult<()> {
|
||||
if exc_val.is_present() || exc_tb.is_present() {
|
||||
crate::warn::warn(
|
||||
vm.ctx.new_str(
|
||||
"the (type, val, tb) signature of throw() is deprecated, \
|
||||
vm.ctx
|
||||
.new_str(
|
||||
"the (type, val, tb) signature of throw() is deprecated, \
|
||||
use throw(val) instead",
|
||||
),
|
||||
)
|
||||
.into(),
|
||||
Some(vm.ctx.exceptions.deprecation_warning.to_owned()),
|
||||
1,
|
||||
None,
|
||||
|
||||
+7
-12
@@ -328,19 +328,14 @@ impl Py<Frame> {
|
||||
}
|
||||
|
||||
pub fn next_external_frame(&self, vm: &VirtualMachine) -> Option<FrameRef> {
|
||||
self.f_back(vm).map(|mut back| {
|
||||
loop {
|
||||
back = if let Some(back) = back.to_owned().f_back(vm) {
|
||||
back
|
||||
} else {
|
||||
break back;
|
||||
};
|
||||
|
||||
if !back.is_internal_frame() {
|
||||
break back;
|
||||
}
|
||||
let mut frame = self.f_back(vm);
|
||||
while let Some(ref f) = frame {
|
||||
if !f.is_internal_frame() {
|
||||
break;
|
||||
}
|
||||
})
|
||||
frame = f.f_back(vm);
|
||||
}
|
||||
frame
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -336,7 +336,7 @@ Support for arbitrary keyword arguments is deprecated and will be removed in Pyt
|
||||
key
|
||||
));
|
||||
warn::warn(
|
||||
message,
|
||||
message.into(),
|
||||
Some(vm.ctx.exceptions.deprecation_warning.to_owned()),
|
||||
1,
|
||||
None,
|
||||
@@ -387,7 +387,7 @@ Support for arbitrary keyword arguments is deprecated and will be removed in Pyt
|
||||
field.as_str()
|
||||
));
|
||||
warn::warn(
|
||||
message,
|
||||
message.into(),
|
||||
Some(vm.ctx.exceptions.deprecation_warning.to_owned()),
|
||||
1,
|
||||
None,
|
||||
|
||||
@@ -224,7 +224,7 @@ fn warn_invalid_escape_sequences_in_format_spec(
|
||||
"\"\\{next}\" is an invalid escape sequence. Such sequences will not work in the future. Did you mean \"\\\\{next}\"? A raw string is also an option."
|
||||
));
|
||||
let _ = warn::warn(
|
||||
message,
|
||||
message.into(),
|
||||
Some(vm.ctx.exceptions.syntax_warning.to_owned()),
|
||||
1,
|
||||
None,
|
||||
|
||||
@@ -20,29 +20,195 @@ pub fn warn(
|
||||
#[pymodule]
|
||||
mod _warnings {
|
||||
use crate::{
|
||||
PyResult, VirtualMachine,
|
||||
builtins::{PyStrRef, PyTypeRef},
|
||||
AsObject, PyObjectRef, PyResult, VirtualMachine,
|
||||
builtins::{PyDictRef, PyListRef, PyStrRef, PyTupleRef, PyTypeRef},
|
||||
convert::TryFromObject,
|
||||
function::OptionalArg,
|
||||
};
|
||||
|
||||
#[pyattr]
|
||||
fn filters(vm: &VirtualMachine) -> PyListRef {
|
||||
vm.state.warnings.filters.clone()
|
||||
}
|
||||
|
||||
#[pyattr(name = "_defaultaction")]
|
||||
fn default_action(vm: &VirtualMachine) -> PyStrRef {
|
||||
vm.state.warnings.default_action.clone()
|
||||
}
|
||||
|
||||
#[pyattr(name = "_onceregistry")]
|
||||
fn once_registry(vm: &VirtualMachine) -> PyDictRef {
|
||||
vm.state.warnings.once_registry.clone()
|
||||
}
|
||||
|
||||
#[pyattr(name = "_warnings_context")]
|
||||
fn warnings_context(vm: &VirtualMachine) -> PyObjectRef {
|
||||
vm.state
|
||||
.warnings
|
||||
.context_var
|
||||
.get_or_init(|| {
|
||||
// Try to create a real ContextVar if _contextvars is available.
|
||||
// During early startup it may not be importable yet, in which
|
||||
// case we fall back to None. This is safe because
|
||||
// context_aware_warnings defaults to False.
|
||||
if let Ok(contextvars) = vm.import("_contextvars", 0)
|
||||
&& let Ok(cv_cls) = contextvars.get_attr("ContextVar", vm)
|
||||
&& let Ok(cv) = cv_cls.call(("_warnings_context",), vm)
|
||||
{
|
||||
cv
|
||||
} else {
|
||||
vm.ctx.none()
|
||||
}
|
||||
})
|
||||
.clone()
|
||||
}
|
||||
|
||||
#[pyfunction(name = "_acquire_lock")]
|
||||
fn acquire_lock(vm: &VirtualMachine) {
|
||||
vm.state.warnings.acquire_lock();
|
||||
}
|
||||
|
||||
#[pyfunction(name = "_release_lock")]
|
||||
fn release_lock(vm: &VirtualMachine) -> PyResult<()> {
|
||||
if !vm.state.warnings.release_lock() {
|
||||
return Err(vm.new_runtime_error("cannot release un-acquired lock".to_owned()));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[pyfunction(name = "_filters_mutated_lock_held")]
|
||||
fn filters_mutated_lock_held(vm: &VirtualMachine) {
|
||||
vm.state.warnings.filters_mutated();
|
||||
}
|
||||
|
||||
#[derive(FromArgs)]
|
||||
struct WarnArgs {
|
||||
#[pyarg(positional)]
|
||||
message: PyStrRef,
|
||||
message: PyObjectRef,
|
||||
#[pyarg(any, optional)]
|
||||
category: OptionalArg<PyTypeRef>,
|
||||
category: OptionalArg<PyObjectRef>,
|
||||
#[pyarg(any, optional)]
|
||||
stacklevel: OptionalArg<u32>,
|
||||
stacklevel: OptionalArg<i32>,
|
||||
#[pyarg(named, optional)]
|
||||
source: OptionalArg<PyObjectRef>,
|
||||
#[pyarg(named, optional)]
|
||||
skip_file_prefixes: OptionalArg<PyTupleRef>,
|
||||
}
|
||||
|
||||
/// Validate and resolve the category argument, matching get_category() in C.
|
||||
fn get_category(
|
||||
message: &PyObjectRef,
|
||||
category: Option<PyObjectRef>,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<Option<PyTypeRef>> {
|
||||
let cat_obj = match category {
|
||||
Some(c) if !vm.is_none(&c) => c,
|
||||
_ => {
|
||||
if message.fast_isinstance(vm.ctx.exceptions.warning) {
|
||||
return Ok(Some(message.class().to_owned()));
|
||||
} else {
|
||||
return Ok(None); // will default to UserWarning in warn_explicit
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let cat = PyTypeRef::try_from_object(vm, cat_obj.clone()).map_err(|_| {
|
||||
vm.new_type_error(format!(
|
||||
"category must be a Warning subclass, not '{}'",
|
||||
cat_obj.class().name()
|
||||
))
|
||||
})?;
|
||||
|
||||
if !cat.fast_issubclass(vm.ctx.exceptions.warning) {
|
||||
return Err(vm.new_type_error(format!(
|
||||
"category must be a Warning subclass, not '{}'",
|
||||
cat.class().name()
|
||||
)));
|
||||
}
|
||||
|
||||
Ok(Some(cat))
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn warn(args: WarnArgs, vm: &VirtualMachine) -> PyResult<()> {
|
||||
let level = args.stacklevel.unwrap_or(1);
|
||||
crate::warn::warn(
|
||||
let level = args.stacklevel.unwrap_or(1) as isize;
|
||||
|
||||
let category = get_category(&args.message, args.category.into_option(), vm)?;
|
||||
|
||||
// Validate skip_file_prefixes: each element must be a str
|
||||
let skip_prefixes = args.skip_file_prefixes.into_option();
|
||||
if let Some(ref prefixes) = skip_prefixes {
|
||||
for item in prefixes.iter() {
|
||||
if !item.class().is(vm.ctx.types.str_type) {
|
||||
return Err(
|
||||
vm.new_type_error("skip_file_prefixes must be a tuple of strs".to_owned())
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
crate::warn::warn_with_skip(
|
||||
args.message,
|
||||
args.category.into_option(),
|
||||
level as isize,
|
||||
None,
|
||||
category,
|
||||
level,
|
||||
args.source.into_option(),
|
||||
skip_prefixes,
|
||||
vm,
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(FromArgs)]
|
||||
struct WarnExplicitArgs {
|
||||
#[pyarg(positional)]
|
||||
message: PyObjectRef,
|
||||
#[pyarg(positional)]
|
||||
category: PyObjectRef,
|
||||
#[pyarg(positional)]
|
||||
filename: PyStrRef,
|
||||
#[pyarg(positional)]
|
||||
lineno: usize,
|
||||
#[pyarg(any, optional)]
|
||||
module: OptionalArg<PyObjectRef>,
|
||||
#[pyarg(any, optional)]
|
||||
registry: OptionalArg<PyObjectRef>,
|
||||
#[pyarg(any, optional)]
|
||||
module_globals: OptionalArg<PyObjectRef>,
|
||||
#[pyarg(named, optional)]
|
||||
source: OptionalArg<PyObjectRef>,
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn warn_explicit(args: WarnExplicitArgs, vm: &VirtualMachine) -> PyResult<()> {
|
||||
let registry = args.registry.into_option().unwrap_or_else(|| vm.ctx.none());
|
||||
|
||||
let module = args.module.into_option();
|
||||
|
||||
// Validate module_globals: must be None or a dict
|
||||
if let Some(ref mg) = args.module_globals.into_option()
|
||||
&& !vm.is_none(mg)
|
||||
&& !mg.class().is(vm.ctx.types.dict_type)
|
||||
{
|
||||
return Err(vm.new_type_error("module_globals must be a dict".to_owned()));
|
||||
}
|
||||
|
||||
let category =
|
||||
if vm.is_none(&args.category) {
|
||||
None
|
||||
} else {
|
||||
Some(PyTypeRef::try_from_object(vm, args.category).map_err(|_| {
|
||||
vm.new_type_error("category must be a Warning subclass".to_owned())
|
||||
})?)
|
||||
};
|
||||
|
||||
crate::warn::warn_explicit(
|
||||
category,
|
||||
args.message,
|
||||
args.filename,
|
||||
args.lineno,
|
||||
module,
|
||||
registry,
|
||||
None, // source_line
|
||||
args.source.into_option(),
|
||||
vm,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -244,7 +244,10 @@ declare_const_name! {
|
||||
// common names
|
||||
_attributes,
|
||||
_fields,
|
||||
_defaultaction,
|
||||
_onceregistry,
|
||||
_showwarnmsg,
|
||||
filters,
|
||||
backslashreplace,
|
||||
close,
|
||||
copy,
|
||||
|
||||
+332
-135
@@ -1,27 +1,69 @@
|
||||
use crate::{
|
||||
AsObject, Context, Py, PyObject, PyObjectRef, PyResult, VirtualMachine,
|
||||
builtins::{
|
||||
PyDictRef, PyListRef, PyStr, PyStrInterned, PyStrRef, PyTuple, PyTupleRef, PyTypeRef,
|
||||
PyBaseExceptionRef, PyDictRef, PyListRef, PyStr, PyStrInterned, PyStrRef, PyTuple,
|
||||
PyTupleRef, PyTypeRef,
|
||||
},
|
||||
convert::{IntoObject, TryFromObject},
|
||||
types::PyComparisonOp,
|
||||
convert::TryFromObject,
|
||||
};
|
||||
use core::sync::atomic::{AtomicUsize, Ordering};
|
||||
use rustpython_common::lock::OnceCell;
|
||||
|
||||
pub struct WarningsState {
|
||||
filters: PyListRef,
|
||||
_once_registry: PyDictRef,
|
||||
default_action: PyStrRef,
|
||||
filters_version: usize,
|
||||
pub filters: PyListRef,
|
||||
pub once_registry: PyDictRef,
|
||||
pub default_action: PyStrRef,
|
||||
pub filters_version: AtomicUsize,
|
||||
pub context_var: OnceCell<PyObjectRef>,
|
||||
lock_count: AtomicUsize,
|
||||
}
|
||||
|
||||
impl WarningsState {
|
||||
fn create_filter(ctx: &Context) -> PyListRef {
|
||||
fn create_default_filters(ctx: &Context) -> PyListRef {
|
||||
// Default filters matching _Py_InitWarningsFilters.
|
||||
// The module field uses plain strings (not regex); check_matched handles
|
||||
// both plain strings (exact comparison) and regex objects (.match()).
|
||||
ctx.new_list(vec![
|
||||
ctx.new_tuple(vec![
|
||||
ctx.new_str("default").into(),
|
||||
ctx.none(),
|
||||
ctx.exceptions.deprecation_warning.as_object().to_owned(),
|
||||
ctx.new_str("__main__").into(),
|
||||
ctx.types.none_type.as_object().to_owned(),
|
||||
ctx.exceptions.warning.as_object().to_owned(),
|
||||
ctx.new_str("ACTION").into(),
|
||||
ctx.new_int(0).into(),
|
||||
])
|
||||
.into(),
|
||||
ctx.new_tuple(vec![
|
||||
ctx.new_str("ignore").into(),
|
||||
ctx.none(),
|
||||
ctx.exceptions.deprecation_warning.as_object().to_owned(),
|
||||
ctx.none(),
|
||||
ctx.new_int(0).into(),
|
||||
])
|
||||
.into(),
|
||||
ctx.new_tuple(vec![
|
||||
ctx.new_str("ignore").into(),
|
||||
ctx.none(),
|
||||
ctx.exceptions
|
||||
.pending_deprecation_warning
|
||||
.as_object()
|
||||
.to_owned(),
|
||||
ctx.none(),
|
||||
ctx.new_int(0).into(),
|
||||
])
|
||||
.into(),
|
||||
ctx.new_tuple(vec![
|
||||
ctx.new_str("ignore").into(),
|
||||
ctx.none(),
|
||||
ctx.exceptions.import_warning.as_object().to_owned(),
|
||||
ctx.none(),
|
||||
ctx.new_int(0).into(),
|
||||
])
|
||||
.into(),
|
||||
ctx.new_tuple(vec![
|
||||
ctx.new_str("ignore").into(),
|
||||
ctx.none(),
|
||||
ctx.exceptions.resource_warning.as_object().to_owned(),
|
||||
ctx.none(),
|
||||
ctx.new_int(0).into(),
|
||||
])
|
||||
.into(),
|
||||
@@ -30,25 +72,53 @@ impl WarningsState {
|
||||
|
||||
pub fn init_state(ctx: &Context) -> Self {
|
||||
Self {
|
||||
filters: Self::create_filter(ctx),
|
||||
_once_registry: ctx.new_dict(),
|
||||
filters: Self::create_default_filters(ctx),
|
||||
once_registry: ctx.new_dict(),
|
||||
default_action: ctx.new_str("default"),
|
||||
filters_version: 0,
|
||||
filters_version: AtomicUsize::new(0),
|
||||
context_var: OnceCell::new(),
|
||||
lock_count: AtomicUsize::new(0),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn acquire_lock(&self) {
|
||||
self.lock_count.fetch_add(1, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
pub fn release_lock(&self) -> bool {
|
||||
let prev = self.lock_count.load(Ordering::SeqCst);
|
||||
if prev == 0 {
|
||||
return false;
|
||||
}
|
||||
self.lock_count.fetch_sub(1, Ordering::SeqCst);
|
||||
true
|
||||
}
|
||||
|
||||
pub fn filters_mutated(&self) {
|
||||
self.filters_version.fetch_add(1, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
/// Match a filter field against an argument.
|
||||
/// - None matches everything
|
||||
/// - Plain strings do exact comparison
|
||||
/// - Regex objects use .match() method
|
||||
fn check_matched(obj: &PyObject, arg: &PyObject, vm: &VirtualMachine) -> PyResult<bool> {
|
||||
if obj.class().is(vm.ctx.types.none_type) {
|
||||
if vm.is_none(obj) {
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
if obj.rich_compare_bool(arg, PyComparisonOp::Eq, vm)? {
|
||||
return Ok(false);
|
||||
// Plain string: exact comparison
|
||||
if obj.class().is(vm.ctx.types.str_type) {
|
||||
let result = obj.rich_compare_bool(arg, crate::types::PyComparisonOp::Eq, vm)?;
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
let result = obj.call((arg.to_owned(),), vm);
|
||||
Ok(result.is_ok())
|
||||
// Regex or other object: call .match() method
|
||||
match vm.call_method(obj, "match", (arg.to_owned(),)) {
|
||||
Ok(result) => Ok(result.is_true(vm)?),
|
||||
Err(_) => Ok(false),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_warnings_attr(
|
||||
@@ -68,7 +138,6 @@ fn get_warnings_attr(
|
||||
}
|
||||
} else {
|
||||
// Check sys.modules for already-imported warnings module
|
||||
// This is what CPython does with PyImport_GetModule
|
||||
match vm.sys_module.get_attr(identifier!(vm, modules), vm) {
|
||||
Ok(modules) => match modules.get_item(vm.ctx.intern_str("warnings"), vm) {
|
||||
Ok(module) => module,
|
||||
@@ -77,62 +146,102 @@ fn get_warnings_attr(
|
||||
Err(_) => return Ok(None),
|
||||
}
|
||||
};
|
||||
Ok(Some(module.get_attr(attr_name, vm)?))
|
||||
match module.get_attr(attr_name, vm) {
|
||||
Ok(attr) => Ok(Some(attr)),
|
||||
Err(_) => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the warnings filters list from sys.modules['warnings'].filters,
|
||||
/// falling back to vm.state.warnings.filters.
|
||||
fn get_warnings_filters(vm: &VirtualMachine) -> PyResult<PyListRef> {
|
||||
if let Ok(Some(filters_obj)) = get_warnings_attr(vm, identifier!(&vm.ctx, filters), false)
|
||||
&& let Ok(filters) = filters_obj.try_into_value::<PyListRef>(vm)
|
||||
{
|
||||
return Ok(filters);
|
||||
}
|
||||
Ok(vm.state.warnings.filters.clone())
|
||||
}
|
||||
|
||||
/// Get the default action from sys.modules['warnings']._defaultaction,
|
||||
/// falling back to vm.state.warnings.default_action.
|
||||
fn get_default_action(vm: &VirtualMachine) -> PyResult<PyObjectRef> {
|
||||
if let Ok(Some(action)) = get_warnings_attr(vm, identifier!(&vm.ctx, _defaultaction), false) {
|
||||
return Ok(action);
|
||||
}
|
||||
Ok(vm.state.warnings.default_action.clone().into())
|
||||
}
|
||||
|
||||
/// Get the once registry from sys.modules['warnings']._onceregistry,
|
||||
/// falling back to vm.state.warnings.once_registry.
|
||||
fn get_once_registry(vm: &VirtualMachine) -> PyResult<PyObjectRef> {
|
||||
if let Ok(Some(registry)) = get_warnings_attr(vm, identifier!(&vm.ctx, _onceregistry), false) {
|
||||
return Ok(registry);
|
||||
}
|
||||
Ok(vm.state.warnings.once_registry.clone().into())
|
||||
}
|
||||
|
||||
/// Called from Rust code to issue a warning via the Python warnings module.
|
||||
pub fn warn(
|
||||
message: PyStrRef,
|
||||
message: PyObjectRef,
|
||||
category: Option<PyTypeRef>,
|
||||
stack_level: isize,
|
||||
source: Option<PyObjectRef>,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<()> {
|
||||
let (filename, lineno, module, registry) = setup_context(stack_level, vm)?;
|
||||
warn_with_skip(message, category, stack_level, source, None, vm)
|
||||
}
|
||||
|
||||
/// warn() with skip_file_prefixes support.
|
||||
pub fn warn_with_skip(
|
||||
message: PyObjectRef,
|
||||
category: Option<PyTypeRef>,
|
||||
mut stack_level: isize,
|
||||
source: Option<PyObjectRef>,
|
||||
skip_file_prefixes: Option<PyTupleRef>,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<()> {
|
||||
// When skip_file_prefixes is active and non-empty, clamp stacklevel to at least 2.
|
||||
if let Some(ref prefixes) = skip_file_prefixes
|
||||
&& !prefixes.is_empty()
|
||||
&& stack_level < 2
|
||||
{
|
||||
stack_level = 2;
|
||||
}
|
||||
let (filename, lineno, module, registry) =
|
||||
setup_context(stack_level, skip_file_prefixes.as_ref(), vm)?;
|
||||
warn_explicit(
|
||||
category, message, filename, lineno, module, registry, None, source, vm,
|
||||
)
|
||||
}
|
||||
|
||||
fn get_default_action(vm: &VirtualMachine) -> PyResult<PyObjectRef> {
|
||||
Ok(vm.state.warnings.default_action.clone().into())
|
||||
// .map_err(|_| {
|
||||
// vm.new_value_error(format!(
|
||||
// "_warnings.defaultaction must be a string, not '{}'",
|
||||
// vm.state.warnings.default_action
|
||||
// ))
|
||||
// })
|
||||
}
|
||||
|
||||
fn get_filter(
|
||||
category: PyObjectRef,
|
||||
text: PyObjectRef,
|
||||
lineno: usize,
|
||||
module: PyObjectRef,
|
||||
mut _item: PyTupleRef,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult {
|
||||
let filters = vm.state.warnings.filters.as_object().to_owned();
|
||||
let filters = get_warnings_filters(vm)?;
|
||||
|
||||
let filters: PyListRef = filters
|
||||
.try_into_value(vm)
|
||||
.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() {
|
||||
let tmp_item = if let Some(tmp_item) = filters.borrow_vec().get(i).cloned() {
|
||||
let tmp_item = PyTupleRef::try_from_object(vm, tmp_item)?;
|
||||
(tmp_item.len() == 5).then_some(tmp_item)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
.ok_or_else(|| vm.new_value_error(format!("_warnings.filters item {i} isn't a 5-tuple")))?;
|
||||
// filters could change while we are iterating over it.
|
||||
// Re-check list length each iteration (matches C behavior).
|
||||
let mut i = 0;
|
||||
while i < filters.borrow_vec().len() {
|
||||
let Some(tmp_item) = filters.borrow_vec().get(i).cloned() else {
|
||||
break;
|
||||
};
|
||||
let tmp_item = PyTupleRef::try_from_object(vm, tmp_item.clone())
|
||||
.ok()
|
||||
.filter(|t| t.len() == 5)
|
||||
.ok_or_else(|| {
|
||||
vm.new_value_error(format!("_warnings.filters item {i} isn't a 5-tuple"))
|
||||
})?;
|
||||
|
||||
/* Python code: action, msg, cat, mod, ln = item */
|
||||
let action = if let Some(action) = tmp_item.first() {
|
||||
action.str_utf8(vm).map(|action| action.into_object())
|
||||
} else {
|
||||
Err(vm.new_type_error("action must be a string"))
|
||||
};
|
||||
let action = tmp_item
|
||||
.first()
|
||||
.ok_or_else(|| vm.new_type_error("action must be a string".to_owned()))?;
|
||||
|
||||
let good_msg = if let Some(msg) = tmp_item.get(1) {
|
||||
check_matched(msg, &text, vm)?
|
||||
@@ -141,7 +250,7 @@ fn get_filter(
|
||||
};
|
||||
|
||||
let is_subclass = if let Some(cat) = tmp_item.get(2) {
|
||||
category.fast_isinstance(cat.class())
|
||||
category.is_subclass(cat, vm)?
|
||||
} else {
|
||||
false
|
||||
};
|
||||
@@ -157,54 +266,58 @@ fn get_filter(
|
||||
});
|
||||
|
||||
if good_msg && good_mod && is_subclass && (ln == 0 || lineno == ln) {
|
||||
_item = tmp_item;
|
||||
return action;
|
||||
return Ok(action.to_owned());
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
|
||||
get_default_action(vm)
|
||||
}
|
||||
|
||||
fn already_warned(
|
||||
registry: PyObjectRef,
|
||||
registry: &PyObject,
|
||||
key: PyObjectRef,
|
||||
should_set: bool,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<bool> {
|
||||
let version_obj = registry.get_item(identifier!(&vm.ctx, version), vm).ok();
|
||||
let filters_version = vm.ctx.new_int(vm.state.warnings.filters_version).into();
|
||||
if vm.is_none(registry) {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
match version_obj {
|
||||
Some(version_obj)
|
||||
if version_obj.try_int(vm).is_ok() || version_obj.is(&filters_version) =>
|
||||
let current_version = vm.state.warnings.filters_version.load(Ordering::SeqCst);
|
||||
let version_obj = registry.get_item(identifier!(&vm.ctx, version), vm).ok();
|
||||
|
||||
let version_matches = version_obj.as_ref().is_some_and(|v| {
|
||||
v.try_int(vm)
|
||||
.map(|i| i.as_u32_mask() as usize == current_version)
|
||||
.unwrap_or(false)
|
||||
});
|
||||
|
||||
if version_matches {
|
||||
// Version matches: check if key is already in the registry
|
||||
if let Ok(val) = registry.get_item(key.as_ref(), vm)
|
||||
&& val.is_true(vm)?
|
||||
{
|
||||
// Use .ok() to handle KeyError when key doesn't exist (like Python's dict.get())
|
||||
if let Ok(already_warned) = registry.get_item(key.as_ref(), vm)
|
||||
&& already_warned.is_true(vm)?
|
||||
{
|
||||
return Ok(true);
|
||||
}
|
||||
return Ok(true); // was already warned
|
||||
}
|
||||
_ => {
|
||||
let registry = registry.dict();
|
||||
if let Some(registry) = registry.as_ref() {
|
||||
registry.clear();
|
||||
let r = registry.set_item("version", filters_version, vm);
|
||||
if r.is_err() {
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Version mismatch or missing: clear registry and set new version
|
||||
if let Ok(dict) = PyDictRef::try_from_object(vm, registry.to_owned()) {
|
||||
dict.clear();
|
||||
let _ = dict.set_item(
|
||||
identifier!(&vm.ctx, version),
|
||||
vm.ctx.new_int(current_version).into(),
|
||||
vm,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/* This warning wasn't found in the registry, set it. */
|
||||
if !should_set {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
let item = vm.ctx.true_value.clone().into();
|
||||
let _ = registry.set_item(key.as_ref(), item, vm); // ignore set error
|
||||
Ok(true)
|
||||
let _ = registry.set_item(key.as_ref(), vm.ctx.true_value.clone().into(), vm);
|
||||
Ok(false) // was NOT previously warned (but now it's recorded)
|
||||
}
|
||||
|
||||
fn normalize_module(filename: &Py<PyStr>, vm: &VirtualMachine) -> Option<PyObjectRef> {
|
||||
@@ -218,10 +331,11 @@ fn normalize_module(filename: &Py<PyStr>, vm: &VirtualMachine) -> Option<PyObjec
|
||||
Some(obj)
|
||||
}
|
||||
|
||||
/// Core warning logic. message can be either a string or a Warning instance.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn warn_explicit(
|
||||
pub(crate) fn warn_explicit(
|
||||
category: Option<PyTypeRef>,
|
||||
message: PyStrRef,
|
||||
message: PyObjectRef,
|
||||
filename: PyStrRef,
|
||||
lineno: usize,
|
||||
module: Option<PyObjectRef>,
|
||||
@@ -230,9 +344,29 @@ fn warn_explicit(
|
||||
source: Option<PyObjectRef>,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<()> {
|
||||
let registry: PyObjectRef = registry
|
||||
.try_into_value(vm)
|
||||
.map_err(|_| vm.new_type_error("'registry' must be a dict or None"))?;
|
||||
// Determine text and category based on whether message is a Warning instance
|
||||
let is_warning = message.fast_isinstance(vm.ctx.exceptions.warning);
|
||||
|
||||
let (text, category) = if is_warning {
|
||||
let text = message.str(vm)?;
|
||||
let cat = message.class().to_owned();
|
||||
(text, cat)
|
||||
} else {
|
||||
// For non-Warning messages, convert to string via str()
|
||||
let text = message.str(vm)?;
|
||||
let cat = if let Some(category) = category {
|
||||
if !category.fast_issubclass(vm.ctx.exceptions.warning) {
|
||||
return Err(vm.new_type_error(format!(
|
||||
"category must be a Warning subclass, not '{}'",
|
||||
category.class().name()
|
||||
)));
|
||||
}
|
||||
category
|
||||
} else {
|
||||
vm.ctx.exceptions.user_warning.to_owned()
|
||||
};
|
||||
(text, cat)
|
||||
};
|
||||
|
||||
// Normalize module.
|
||||
let module = match module.or_else(|| normalize_module(&filename, vm)) {
|
||||
@@ -240,76 +374,110 @@ fn warn_explicit(
|
||||
None => return Ok(()),
|
||||
};
|
||||
|
||||
// Normalize message.
|
||||
let text = message.as_wtf8();
|
||||
|
||||
let category = if let Some(category) = category {
|
||||
if !category.fast_issubclass(vm.ctx.exceptions.warning) {
|
||||
return Err(vm.new_type_error(format!(
|
||||
"category must be a Warning subclass, not '{}'",
|
||||
category.class().name()
|
||||
)));
|
||||
}
|
||||
category
|
||||
} else {
|
||||
vm.ctx.exceptions.user_warning.to_owned()
|
||||
};
|
||||
|
||||
let category = if message.fast_isinstance(vm.ctx.exceptions.warning) {
|
||||
message.class().to_owned()
|
||||
} else {
|
||||
category
|
||||
};
|
||||
|
||||
// Create key.
|
||||
let key = PyTuple::new_ref(
|
||||
// Create key: (text, category, lineno) - used for "default" and "module" actions
|
||||
let key: PyObjectRef = PyTuple::new_ref(
|
||||
vec![
|
||||
vm.ctx.new_int(3).into(),
|
||||
vm.ctx.new_str(text).into(),
|
||||
text.clone().into(),
|
||||
category.as_object().to_owned(),
|
||||
vm.ctx.new_int(lineno).into(),
|
||||
],
|
||||
&vm.ctx,
|
||||
);
|
||||
)
|
||||
.into();
|
||||
|
||||
if !vm.is_none(registry.as_object()) && already_warned(registry, key.into_object(), false, vm)?
|
||||
{
|
||||
// Check if already warned
|
||||
if !vm.is_none(®istry) && already_warned(®istry, key.clone(), false, vm)? {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let item = vm.ctx.new_tuple(vec![]);
|
||||
// Get filter action
|
||||
let action = get_filter(
|
||||
category.as_object().to_owned(),
|
||||
vm.ctx.new_str(text).into(),
|
||||
text.clone().into(),
|
||||
lineno,
|
||||
module,
|
||||
item,
|
||||
vm,
|
||||
)?;
|
||||
|
||||
if action.str_utf8(vm)?.as_str().eq("error") {
|
||||
return Err(vm.new_type_error(message.to_string()));
|
||||
let action_str = PyStrRef::try_from_object(vm, action)
|
||||
.map_err(|_| vm.new_type_error("action must be a string".to_owned()))?;
|
||||
|
||||
match action_str.as_str() {
|
||||
"error" => {
|
||||
// Raise the Warning as an exception
|
||||
let exc = if is_warning {
|
||||
PyBaseExceptionRef::try_from_object(vm, message)?
|
||||
} else {
|
||||
vm.invoke_exception(category.clone(), vec![text.into()])?
|
||||
};
|
||||
return Err(exc);
|
||||
}
|
||||
"ignore" => return Ok(()),
|
||||
"once" => {
|
||||
// "once" uses (text, category) as key — no lineno
|
||||
let once_key: PyObjectRef = PyTuple::new_ref(
|
||||
vec![text.clone().into(), category.as_object().to_owned()],
|
||||
&vm.ctx,
|
||||
)
|
||||
.into();
|
||||
let reg = get_once_registry(vm)?;
|
||||
if already_warned(®, once_key, true, vm)? {
|
||||
return Ok(()); // already warned once
|
||||
}
|
||||
}
|
||||
"always" | "all" => { /* fall through to show warning */ }
|
||||
"module" => {
|
||||
if !vm.is_none(®istry) {
|
||||
// Record with the full key (text, category, lineno)
|
||||
already_warned(®istry, key, true, vm)?;
|
||||
// Check/set altkey (text, category) — without lineno.
|
||||
// If the altkey is already recorded, suppress.
|
||||
let alt_key: PyObjectRef = PyTuple::new_ref(
|
||||
vec![text.clone().into(), category.as_object().to_owned()],
|
||||
&vm.ctx,
|
||||
)
|
||||
.into();
|
||||
if already_warned(®istry, alt_key, true, vm)? {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
"default" => {
|
||||
if !vm.is_none(®istry) && already_warned(®istry, key, true, vm)? {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
other => {
|
||||
return Err(vm.new_runtime_error(format!(
|
||||
"Unrecognized action ({other}) in warnings.filters:\n {other}"
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
if action.str_utf8(vm)?.as_str().eq("ignore") {
|
||||
return Ok(());
|
||||
}
|
||||
// Create Warning instance if message is a string
|
||||
let warning_instance = if is_warning {
|
||||
message
|
||||
} else {
|
||||
category.as_object().call((text.clone(),), vm)?
|
||||
};
|
||||
|
||||
call_show_warning(
|
||||
// t_state,
|
||||
category,
|
||||
message,
|
||||
text,
|
||||
warning_instance,
|
||||
filename,
|
||||
lineno, // lineno_obj,
|
||||
lineno,
|
||||
source_line,
|
||||
source,
|
||||
vm,
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn call_show_warning(
|
||||
category: PyTypeRef,
|
||||
message: PyStrRef,
|
||||
text: PyStrRef,
|
||||
warning_instance: PyObjectRef,
|
||||
filename: PyStrRef,
|
||||
lineno: usize,
|
||||
source_line: Option<PyObjectRef>,
|
||||
@@ -319,20 +487,18 @@ fn call_show_warning(
|
||||
let Some(show_fn) =
|
||||
get_warnings_attr(vm, identifier!(&vm.ctx, _showwarnmsg), source.is_some())?
|
||||
else {
|
||||
return show_warning(filename, lineno, message, category, source_line, vm);
|
||||
return show_warning(filename, lineno, text, category, source_line, vm);
|
||||
};
|
||||
if !show_fn.is_callable() {
|
||||
return Err(vm.new_type_error("warnings._showwarnmsg() must be set to a callable"));
|
||||
return Err(
|
||||
vm.new_type_error("warnings._showwarnmsg() must be set to a callable".to_owned())
|
||||
);
|
||||
}
|
||||
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"));
|
||||
return Err(vm.new_type_error("unable to get warnings.WarningMessage".to_owned()));
|
||||
};
|
||||
|
||||
// Create a Warning instance by calling category(message)
|
||||
// This is what warnings module does
|
||||
let warning_instance = category.as_object().call((message,), vm)?;
|
||||
|
||||
let msg = warnmsg_cls.call(
|
||||
vec![
|
||||
warning_instance,
|
||||
@@ -362,10 +528,41 @@ fn show_warning(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check if a frame's filename starts with any of the given prefixes.
|
||||
fn is_filename_to_skip(frame: &crate::frame::Frame, prefixes: &PyTupleRef) -> bool {
|
||||
let filename = frame.f_code().co_filename();
|
||||
let filename_s = filename.as_str();
|
||||
prefixes.iter().any(|prefix| {
|
||||
prefix
|
||||
.downcast_ref::<PyStr>()
|
||||
.is_some_and(|s| filename_s.starts_with(s.as_str()))
|
||||
})
|
||||
}
|
||||
|
||||
/// Like Frame::next_external_frame but also skips frames matching prefixes.
|
||||
fn next_external_frame_with_skip(
|
||||
frame: &crate::frame::FrameRef,
|
||||
skip_file_prefixes: Option<&PyTupleRef>,
|
||||
vm: &VirtualMachine,
|
||||
) -> Option<crate::frame::FrameRef> {
|
||||
let mut f = frame.f_back(vm);
|
||||
loop {
|
||||
let current: crate::frame::FrameRef = f.take()?;
|
||||
let should_skip = current.is_internal_frame()
|
||||
|| skip_file_prefixes.is_some_and(|prefixes| is_filename_to_skip(¤t, prefixes));
|
||||
if should_skip {
|
||||
f = current.f_back(vm);
|
||||
} else {
|
||||
return Some(current);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// filename, module, and registry are new refs, globals is borrowed
|
||||
/// Returns `Ok` on success, or `Err` on error (no new refs)
|
||||
fn setup_context(
|
||||
mut stack_level: isize,
|
||||
skip_file_prefixes: Option<&PyTupleRef>,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<
|
||||
// filename, lineno, module, registry
|
||||
@@ -397,7 +594,7 @@ fn setup_context(
|
||||
break;
|
||||
}
|
||||
if let Some(tmp) = f {
|
||||
f = tmp.next_external_frame(vm);
|
||||
f = next_external_frame_with_skip(&tmp, skip_file_prefixes, vm);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
@@ -417,7 +614,7 @@ fn setup_context(
|
||||
.get_attr(identifier!(vm, __dict__), vm)
|
||||
.and_then(|d| {
|
||||
d.downcast::<crate::builtins::PyDict>()
|
||||
.map_err(|_| vm.new_type_error("sys.__dict__ is not a dictionary"))
|
||||
.map_err(|_| vm.new_type_error("sys.__dict__ is not a dictionary".to_owned()))
|
||||
})?;
|
||||
(globals, vm.ctx.intern_str("<sys>"), 0)
|
||||
};
|
||||
|
||||
@@ -355,6 +355,17 @@ pub fn parse_opts() -> Result<(Settings, RunMode), lexopt::Error> {
|
||||
};
|
||||
settings.warnoptions.push(warn.to_owned());
|
||||
}
|
||||
if let Some(val) = get_env("PYTHONWARNINGS")
|
||||
&& let Some(val_str) = val.to_str()
|
||||
&& !val_str.is_empty()
|
||||
{
|
||||
for warning in val_str.split(',') {
|
||||
let warning = warning.trim();
|
||||
if !warning.is_empty() {
|
||||
settings.warnoptions.push(warning.to_owned());
|
||||
}
|
||||
}
|
||||
}
|
||||
settings.warnoptions.extend(args.warning_control);
|
||||
|
||||
settings.hash_seed = match (!args.random_hash_seed)
|
||||
|
||||
Reference in new issue
Block a user