mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 10:44:36 +08:00
Refactor warn.rs and _warnings module (#7023)
- Add already_warned() with filter version tracking - Add type validation for _defaultaction and _onceregistry - Use direct function names for _warnings pyattr/pyfunction - Route stdlib::warnings::warn through warn::warn - Remove spurious EncodingWarning from TextIOWrapper - Clean up comments and simplify check_matched error handling - Remove expectedFailure from 3 passing test_warnings tests
This commit is contained in:
1 parent
ef41555307
commit
58831550c4
5 files changed
+257
-285
No files matched your search
Vendored
-3
@@ -929,7 +929,6 @@ class _WarningsTests(BaseTest, unittest.TestCase):
|
||||
self.assertRaises(UserWarning, self.module.warn,
|
||||
'convert to error')
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'warnings' has no attribute 'onceregistry'
|
||||
def test_onceregistry(self):
|
||||
# Replacing or removing the onceregistry should be okay.
|
||||
global __warningregistry__
|
||||
@@ -959,7 +958,6 @@ class _WarningsTests(BaseTest, unittest.TestCase):
|
||||
finally:
|
||||
self.module.onceregistry = original_registry
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'warnings' has no attribute 'defaultaction'
|
||||
def test_default_action(self):
|
||||
# Replacing or removing defaultaction should be okay.
|
||||
message = UserWarning("defaultaction test")
|
||||
@@ -1631,7 +1629,6 @@ a=A()
|
||||
self.assertEqual(err.decode().rstrip(),
|
||||
'<string>:7: UserWarning: test')
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: b'' doesn't start with b'<sys>:0: ResourceWarning: unclosed file '
|
||||
def test_late_resource_warning(self):
|
||||
# Issue #21925: Emitting a ResourceWarning late during the Python
|
||||
# shutdown must be logged.
|
||||
|
||||
@@ -2808,14 +2808,10 @@ mod _io {
|
||||
encoding: Option<PyUtf8StrRef>,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<PyUtf8StrRef> {
|
||||
if encoding.is_none() && vm.state.config.settings.warn_default_encoding {
|
||||
crate::stdlib::warnings::warn(
|
||||
vm.ctx.exceptions.encoding_warning,
|
||||
"'encoding' argument not specified".to_owned(),
|
||||
1,
|
||||
vm,
|
||||
)?;
|
||||
}
|
||||
// Note: Do not issue EncodingWarning here. The warning should only
|
||||
// be issued by io.text_encoding(), the public API. This function
|
||||
// is used internally (e.g., for stdin/stdout/stderr initialization)
|
||||
// where no warning should be emitted.
|
||||
let encoding = match encoding {
|
||||
None if vm.state.config.settings.utf8_mode > 0 => {
|
||||
identifier_utf8!(vm, utf_8).to_owned()
|
||||
|
||||
@@ -8,13 +8,13 @@ pub fn warn(
|
||||
stack_level: usize,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<()> {
|
||||
// TODO: use rust warnings module
|
||||
if let Ok(module) = vm.import("warnings", 0)
|
||||
&& let Ok(func) = module.get_attr("warn", vm)
|
||||
{
|
||||
func.call((message, category.to_owned(), stack_level), vm)?;
|
||||
}
|
||||
Ok(())
|
||||
crate::warn::warn(
|
||||
vm.new_pyobj(message),
|
||||
Some(category.to_owned()),
|
||||
isize::try_from(stack_level).unwrap_or(isize::MAX),
|
||||
None,
|
||||
vm,
|
||||
)
|
||||
}
|
||||
|
||||
#[pymodule]
|
||||
@@ -31,18 +31,18 @@ mod _warnings {
|
||||
vm.state.warnings.filters.clone()
|
||||
}
|
||||
|
||||
#[pyattr(name = "_defaultaction")]
|
||||
fn default_action(vm: &VirtualMachine) -> PyStrRef {
|
||||
#[pyattr]
|
||||
fn _defaultaction(vm: &VirtualMachine) -> PyStrRef {
|
||||
vm.state.warnings.default_action.clone()
|
||||
}
|
||||
|
||||
#[pyattr(name = "_onceregistry")]
|
||||
fn once_registry(vm: &VirtualMachine) -> PyDictRef {
|
||||
#[pyattr]
|
||||
fn _onceregistry(vm: &VirtualMachine) -> PyDictRef {
|
||||
vm.state.warnings.once_registry.clone()
|
||||
}
|
||||
|
||||
#[pyattr(name = "_warnings_context")]
|
||||
fn warnings_context(vm: &VirtualMachine) -> PyObjectRef {
|
||||
#[pyattr]
|
||||
fn _warnings_context(vm: &VirtualMachine) -> PyObjectRef {
|
||||
vm.state
|
||||
.warnings
|
||||
.context_var
|
||||
@@ -63,21 +63,21 @@ mod _warnings {
|
||||
.clone()
|
||||
}
|
||||
|
||||
#[pyfunction(name = "_acquire_lock")]
|
||||
fn acquire_lock(vm: &VirtualMachine) {
|
||||
#[pyfunction]
|
||||
fn _acquire_lock(vm: &VirtualMachine) {
|
||||
vm.state.warnings.acquire_lock();
|
||||
}
|
||||
|
||||
#[pyfunction(name = "_release_lock")]
|
||||
fn release_lock(vm: &VirtualMachine) -> PyResult<()> {
|
||||
#[pyfunction]
|
||||
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) {
|
||||
#[pyfunction]
|
||||
fn _filters_mutated_lock_held(vm: &VirtualMachine) {
|
||||
vm.state.warnings.filters_mutated();
|
||||
}
|
||||
|
||||
|
||||
@@ -247,6 +247,8 @@ declare_const_name! {
|
||||
_defaultaction,
|
||||
_onceregistry,
|
||||
_showwarnmsg,
|
||||
defaultaction,
|
||||
onceregistry,
|
||||
filters,
|
||||
backslashreplace,
|
||||
close,
|
||||
|
||||
+232
-255
@@ -20,9 +20,7 @@ pub struct WarningsState {
|
||||
|
||||
impl WarningsState {
|
||||
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()).
|
||||
// init_filters(): non-debug default filter set.
|
||||
ctx.new_list(vec![
|
||||
ctx.new_tuple(vec![
|
||||
ctx.new_str("default").into(),
|
||||
@@ -99,26 +97,17 @@ impl WarningsState {
|
||||
}
|
||||
}
|
||||
|
||||
/// Match a filter field against an argument.
|
||||
/// - None matches everything
|
||||
/// - Plain strings do exact comparison
|
||||
/// - Regex objects use .match() method
|
||||
/// None matches everything; plain strings do exact comparison;
|
||||
/// regex objects use .match().
|
||||
fn check_matched(obj: &PyObject, arg: &PyObject, vm: &VirtualMachine) -> PyResult<bool> {
|
||||
if vm.is_none(obj) {
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
// 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),
|
||||
return obj.rich_compare_bool(arg, crate::types::PyComparisonOp::Eq, vm);
|
||||
}
|
||||
let result = vm.call_method(obj, "match", (arg.to_owned(),))?;
|
||||
result.is_true(vm)
|
||||
}
|
||||
|
||||
fn get_warnings_attr(
|
||||
@@ -137,7 +126,6 @@ fn get_warnings_attr(
|
||||
Err(_) => return Ok(None),
|
||||
}
|
||||
} else {
|
||||
// Check sys.modules for already-imported warnings module
|
||||
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,
|
||||
@@ -155,7 +143,7 @@ fn get_warnings_attr(
|
||||
/// 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)
|
||||
if let 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);
|
||||
@@ -166,7 +154,13 @@ fn get_warnings_filters(vm: &VirtualMachine) -> PyResult<PyListRef> {
|
||||
/// 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) {
|
||||
if let Some(action) = get_warnings_attr(vm, identifier!(&vm.ctx, defaultaction), false)? {
|
||||
if !action.class().is(vm.ctx.types.str_type) {
|
||||
return Err(vm.new_type_error(format!(
|
||||
"_warnings.defaultaction must be a string, not '{}'",
|
||||
action.class().name()
|
||||
)));
|
||||
}
|
||||
return Ok(action);
|
||||
}
|
||||
Ok(vm.state.warnings.default_action.clone().into())
|
||||
@@ -175,105 +169,18 @@ fn get_default_action(vm: &VirtualMachine) -> PyResult<PyObjectRef> {
|
||||
/// 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) {
|
||||
if let Some(registry) = get_warnings_attr(vm, identifier!(&vm.ctx, onceregistry), false)? {
|
||||
if !registry.class().is(vm.ctx.types.dict_type) {
|
||||
return Err(vm.new_type_error(format!(
|
||||
"_warnings.onceregistry must be a dict, not '{}'",
|
||||
registry.class().name()
|
||||
)));
|
||||
}
|
||||
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: PyObjectRef,
|
||||
category: Option<PyTypeRef>,
|
||||
stack_level: isize,
|
||||
source: Option<PyObjectRef>,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<()> {
|
||||
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_filter(
|
||||
category: PyObjectRef,
|
||||
text: PyObjectRef,
|
||||
lineno: usize,
|
||||
module: PyObjectRef,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult {
|
||||
let filters = get_warnings_filters(vm)?;
|
||||
|
||||
// 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 = 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)?
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
let is_subclass = if let Some(cat) = tmp_item.get(2) {
|
||||
category.is_subclass(cat, vm)?
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
let good_mod = if let Some(item_mod) = tmp_item.get(3) {
|
||||
check_matched(item_mod, &module, vm)?
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
let ln = tmp_item.get(4).map_or(0, |ln_obj| {
|
||||
ln_obj.try_int(vm).map_or(0, |ln| ln.as_u32_mask() as _)
|
||||
});
|
||||
|
||||
if good_msg && good_mod && is_subclass && (ln == 0 || lineno == ln) {
|
||||
return Ok(action.to_owned());
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
|
||||
get_default_action(vm)
|
||||
}
|
||||
|
||||
fn already_warned(
|
||||
registry: &PyObject,
|
||||
key: PyObjectRef,
|
||||
@@ -294,44 +201,136 @@ fn already_warned(
|
||||
});
|
||||
|
||||
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)?
|
||||
{
|
||||
return Ok(true); // was already warned
|
||||
}
|
||||
} 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,
|
||||
);
|
||||
return Ok(true);
|
||||
}
|
||||
} else if let Ok(dict) = PyDictRef::try_from_object(vm, registry.to_owned()) {
|
||||
dict.clear();
|
||||
dict.set_item(
|
||||
identifier!(&vm.ctx, version),
|
||||
vm.ctx.new_int(current_version).into(),
|
||||
vm,
|
||||
)?;
|
||||
}
|
||||
|
||||
if !should_set {
|
||||
return Ok(false);
|
||||
if should_set {
|
||||
registry.set_item(key.as_ref(), vm.ctx.true_value.clone().into(), vm)?;
|
||||
}
|
||||
|
||||
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)
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
fn normalize_module(filename: &Py<PyStr>, vm: &VirtualMachine) -> Option<PyObjectRef> {
|
||||
let obj = match filename.char_len() {
|
||||
/// Create a `(text, category)` or `(text, category, 0)` key and record
|
||||
/// it in the registry via `already_warned`.
|
||||
fn update_registry(
|
||||
registry: &PyObject,
|
||||
text: &PyObject,
|
||||
category: &PyObject,
|
||||
add_zero: bool,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<bool> {
|
||||
let altkey: PyObjectRef = if add_zero {
|
||||
PyTuple::new_ref(
|
||||
vec![
|
||||
text.to_owned(),
|
||||
category.to_owned(),
|
||||
vm.ctx.new_int(0).into(),
|
||||
],
|
||||
&vm.ctx,
|
||||
)
|
||||
.into()
|
||||
} else {
|
||||
PyTuple::new_ref(vec![text.to_owned(), category.to_owned()], &vm.ctx).into()
|
||||
};
|
||||
already_warned(registry, altkey, true, vm)
|
||||
}
|
||||
|
||||
fn normalize_module(filename: &Py<PyStr>, vm: &VirtualMachine) -> PyObjectRef {
|
||||
match filename.byte_len() {
|
||||
0 => vm.new_pyobj("<unknown>"),
|
||||
len if len >= 3 && filename.as_bytes().ends_with(b".py") => {
|
||||
vm.new_pyobj(&filename.as_wtf8()[..len - 3])
|
||||
}
|
||||
_ => filename.as_object().to_owned(),
|
||||
};
|
||||
Some(obj)
|
||||
}
|
||||
}
|
||||
|
||||
/// Core warning logic. message can be either a string or a Warning instance.
|
||||
/// Search the global filters list for a matching action.
|
||||
// TODO: split into filter_search() + get_filter() and support
|
||||
// context-aware filters (get_warnings_context_filters).
|
||||
fn get_filter(
|
||||
category: PyObjectRef,
|
||||
text: PyObjectRef,
|
||||
lineno: usize,
|
||||
module: PyObjectRef,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult {
|
||||
let filters = get_warnings_filters(vm)?;
|
||||
|
||||
// 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)
|
||||
.ok()
|
||||
.filter(|t| t.len() == 5)
|
||||
.ok_or_else(|| {
|
||||
vm.new_value_error(format!("_warnings.filters item {i} isn't a 5-tuple"))
|
||||
})?;
|
||||
|
||||
/* action, msg, cat, mod, ln = item */
|
||||
let action = &tmp_item[0];
|
||||
let good_msg = check_matched(&tmp_item[1], &text, vm)?;
|
||||
let is_subclass = category.is_subclass(&tmp_item[2], vm)?;
|
||||
let good_mod = check_matched(&tmp_item[3], &module, vm)?;
|
||||
let ln: usize = tmp_item[4].try_int(vm).map_or(0, |v| v.as_u32_mask() as _);
|
||||
|
||||
if good_msg && is_subclass && good_mod && (ln == 0 || lineno == ln) {
|
||||
return Ok(action.to_owned());
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
|
||||
get_default_action(vm)
|
||||
}
|
||||
|
||||
pub fn warn(
|
||||
message: PyObjectRef,
|
||||
category: Option<PyTypeRef>,
|
||||
stack_level: isize,
|
||||
source: Option<PyObjectRef>,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<()> {
|
||||
warn_with_skip(message, category, stack_level, source, None, vm)
|
||||
}
|
||||
|
||||
/// do_warn: resolve context via setup_context, then call warn_explicit.
|
||||
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<()> {
|
||||
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,
|
||||
)
|
||||
}
|
||||
|
||||
/// Core warning logic matching `warn_explicit()` in `_warnings.c`.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn warn_explicit(
|
||||
category: Option<PyTypeRef>,
|
||||
@@ -344,42 +343,34 @@ pub(crate) fn warn_explicit(
|
||||
source: Option<PyObjectRef>,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<()> {
|
||||
// Determine text and category based on whether message is a Warning instance
|
||||
let is_warning = message.fast_isinstance(vm.ctx.exceptions.warning);
|
||||
// Normalize module. None → silent return (late-shutdown safety).
|
||||
let module = module.unwrap_or_else(|| normalize_module(&filename, vm));
|
||||
if vm.is_none(&module) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let (text, category) = if is_warning {
|
||||
// Normalize message.
|
||||
let is_warning = message.fast_isinstance(vm.ctx.exceptions.warning);
|
||||
let (text, category, message) = if is_warning {
|
||||
let text = message.str(vm)?;
|
||||
let cat = message.class().to_owned();
|
||||
(text, cat)
|
||||
(text, cat, message)
|
||||
} 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)
|
||||
let cat = category.unwrap_or_else(|| vm.ctx.exceptions.user_warning.to_owned());
|
||||
let instance = cat.as_object().call((text.clone(),), vm)?;
|
||||
(text, cat, instance)
|
||||
};
|
||||
|
||||
// Normalize module.
|
||||
let module = match module.or_else(|| normalize_module(&filename, vm)) {
|
||||
Some(module) => module,
|
||||
None => return Ok(()),
|
||||
};
|
||||
let lineno_obj: PyObjectRef = vm.ctx.new_int(lineno).into();
|
||||
|
||||
// Create key: (text, category, lineno) - used for "default" and "module" actions
|
||||
// key = (text, category, lineno)
|
||||
let key: PyObjectRef = PyTuple::new_ref(
|
||||
vec![
|
||||
text.clone().into(),
|
||||
category.as_object().to_owned(),
|
||||
vm.ctx.new_int(lineno).into(),
|
||||
lineno_obj.clone(),
|
||||
],
|
||||
&vm.ctx,
|
||||
)
|
||||
@@ -398,75 +389,62 @@ pub(crate) fn warn_explicit(
|
||||
module,
|
||||
vm,
|
||||
)?;
|
||||
|
||||
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.as_str() == "error" {
|
||||
let exc = PyBaseExceptionRef::try_from_object(vm, message)?;
|
||||
return Err(exc);
|
||||
}
|
||||
if action_str.as_str() == "ignore" {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Create Warning instance if message is a string
|
||||
let warning_instance = if is_warning {
|
||||
message
|
||||
// For everything except "always"/"all", record in registry then
|
||||
// check per-action registries.
|
||||
let already = if action_str.as_str() != "always" && action_str.as_str() != "all" {
|
||||
if !vm.is_none(®istry) {
|
||||
registry.set_item(&*key, vm.ctx.true_value.clone().into(), vm)?;
|
||||
}
|
||||
|
||||
match action_str.as_str() {
|
||||
"once" => {
|
||||
let reg = if vm.is_none(®istry) {
|
||||
get_once_registry(vm)?
|
||||
} else {
|
||||
registry.clone()
|
||||
};
|
||||
update_registry(®, text.as_ref(), category.as_object(), false, vm)?
|
||||
}
|
||||
"module" => {
|
||||
if !vm.is_none(®istry) {
|
||||
update_registry(®istry, text.as_ref(), category.as_object(), false, vm)?
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
"default" => false,
|
||||
other => {
|
||||
return Err(vm.new_runtime_error(format!(
|
||||
"Unrecognized action ({other}) in warnings.filters:\n {other}"
|
||||
)));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
category.as_object().call((text.clone(),), vm)?
|
||||
false
|
||||
};
|
||||
|
||||
if already {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
call_show_warning(
|
||||
category,
|
||||
text,
|
||||
warning_instance,
|
||||
message,
|
||||
filename,
|
||||
lineno,
|
||||
lineno_obj,
|
||||
source_line,
|
||||
source,
|
||||
vm,
|
||||
@@ -477,9 +455,10 @@ pub(crate) fn warn_explicit(
|
||||
fn call_show_warning(
|
||||
category: PyTypeRef,
|
||||
text: PyStrRef,
|
||||
warning_instance: PyObjectRef,
|
||||
message: PyObjectRef,
|
||||
filename: PyStrRef,
|
||||
lineno: usize,
|
||||
lineno_obj: PyObjectRef,
|
||||
source_line: Option<PyObjectRef>,
|
||||
source: Option<PyObjectRef>,
|
||||
vm: &VirtualMachine,
|
||||
@@ -496,15 +475,15 @@ fn call_show_warning(
|
||||
}
|
||||
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_runtime_error("unable to get warnings.WarningMessage".to_owned()));
|
||||
};
|
||||
|
||||
let msg = warnmsg_cls.call(
|
||||
vec![
|
||||
warning_instance,
|
||||
message,
|
||||
category.into(),
|
||||
filename.into(),
|
||||
vm.new_pyobj(lineno),
|
||||
lineno_obj,
|
||||
vm.ctx.none(),
|
||||
vm.ctx.none(),
|
||||
vm.unwrap_or_none(source),
|
||||
@@ -516,15 +495,22 @@ fn call_show_warning(
|
||||
}
|
||||
|
||||
fn show_warning(
|
||||
_filename: PyStrRef,
|
||||
_lineno: usize,
|
||||
filename: PyStrRef,
|
||||
lineno: usize,
|
||||
text: PyStrRef,
|
||||
category: PyTypeRef,
|
||||
_source_line: Option<PyObjectRef>,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<()> {
|
||||
let stderr = crate::stdlib::sys::PyStderr(vm);
|
||||
writeln!(stderr, "{}: {}", category.name(), text);
|
||||
writeln!(
|
||||
stderr,
|
||||
"{}:{}: {}: {}",
|
||||
filename,
|
||||
lineno,
|
||||
category.name(),
|
||||
text
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -548,9 +534,9 @@ fn next_external_frame_with_skip(
|
||||
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 {
|
||||
if current.is_internal_frame()
|
||||
|| skip_file_prefixes.is_some_and(|p| is_filename_to_skip(¤t, p))
|
||||
{
|
||||
f = current.f_back(vm);
|
||||
} else {
|
||||
return Some(current);
|
||||
@@ -564,39 +550,29 @@ fn setup_context(
|
||||
mut stack_level: isize,
|
||||
skip_file_prefixes: Option<&PyTupleRef>,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<
|
||||
// filename, lineno, module, registry
|
||||
(PyStrRef, usize, Option<PyObjectRef>, PyObjectRef),
|
||||
> {
|
||||
let __warningregistry__ = "__warningregistry__";
|
||||
let __name__ = "__name__";
|
||||
|
||||
) -> PyResult<(PyStrRef, usize, Option<PyObjectRef>, PyObjectRef)> {
|
||||
let mut f = vm.current_frame().as_deref().cloned();
|
||||
|
||||
// Stack level comparisons to Python code is off by one as there is no
|
||||
// warnings-related stack level to avoid.
|
||||
if stack_level <= 0 || f.as_ref().is_some_and(|frame| frame.is_internal_frame()) {
|
||||
loop {
|
||||
while {
|
||||
stack_level -= 1;
|
||||
if stack_level <= 0 {
|
||||
break;
|
||||
}
|
||||
if let Some(tmp) = f {
|
||||
f = tmp.f_back(vm);
|
||||
} else {
|
||||
break;
|
||||
stack_level > 0
|
||||
} {
|
||||
match f {
|
||||
Some(tmp) => f = tmp.f_back(vm),
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
loop {
|
||||
while {
|
||||
stack_level -= 1;
|
||||
if stack_level <= 0 {
|
||||
break;
|
||||
}
|
||||
if let Some(tmp) = f {
|
||||
f = next_external_frame_with_skip(&tmp, skip_file_prefixes, vm);
|
||||
} else {
|
||||
break;
|
||||
stack_level > 0
|
||||
} {
|
||||
match f {
|
||||
Some(tmp) => f = next_external_frame_with_skip(&tmp, skip_file_prefixes, vm),
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -619,17 +595,18 @@ fn setup_context(
|
||||
(globals, vm.ctx.intern_str("<sys>"), 0)
|
||||
};
|
||||
|
||||
let registry = if let Ok(registry) = globals.get_item(__warningregistry__, vm) {
|
||||
registry
|
||||
} else {
|
||||
let registry = vm.ctx.new_dict();
|
||||
globals.set_item(__warningregistry__, registry.clone().into(), vm)?;
|
||||
registry.into()
|
||||
let registry = match globals.get_item("__warningregistry__", vm) {
|
||||
Ok(r) => r,
|
||||
Err(_) => {
|
||||
let r = vm.ctx.new_dict();
|
||||
globals.set_item("__warningregistry__", r.clone().into(), vm)?;
|
||||
r.into()
|
||||
}
|
||||
};
|
||||
|
||||
// Setup module.
|
||||
let module = globals
|
||||
.get_item(__name__, vm)
|
||||
.get_item("__name__", vm)
|
||||
.unwrap_or_else(|_| vm.new_pyobj("<string>"));
|
||||
Ok((filename.to_owned(), lineno, Some(module), registry))
|
||||
}
|
||||
Reference in new issue
Block a user