Fix final 3.15 code names, warning filters and constant folding

Retain public co_name subclass owners for comparison and hashing. Match omitted warning modules against filenames through the exact final CPython helper imported with update_lib. Port final constant sequence folding order and align the string-format diagnostics exposed by canonical CI.

Assisted-by: OpenAI Codex:model identifier unavailable
This commit is contained in:
dot committed 2026-10-13 00:20:16 +09:00
1 parent 476bb74f3c
commit 6482adf66f
6 files changed
+257 -78

No files matched your search

+83 -24
View File
@@ -369,9 +369,15 @@ def _setoption(arg):
if message or module: if message or module:
import re import re
if message: if message:
message = re.escape(message) if len(message) >= 2 and message[0] == message[-1] == '/':
message = message[1:-1]
else:
message = re.escape(message)
if module: if module:
module = re.escape(module) + r'\z' if len(module) >= 2 and module[0] == module[-1] == '/':
module = module[1:-1]
else:
module = re.escape(module) + r'\z'
if lineno: if lineno:
try: try:
lineno = int(lineno) lineno = int(lineno)
@@ -381,7 +387,23 @@ def _setoption(arg):
raise _wm._OptionError("invalid lineno %r" % (lineno,)) from None raise _wm._OptionError("invalid lineno %r" % (lineno,)) from None
else: else:
lineno = 0 lineno = 0
_wm.filterwarnings(action, message, category, module, lineno) try:
_wm.filterwarnings(action, message, category, module, lineno)
except re.PatternError if message or module else ():
if message:
try:
re.compile(message)
except re.PatternError:
raise _wm._OptionError(f"invalid regular expression for "
f"message: {message!r}") from None
if module:
try:
re.compile(module)
except re.PatternError:
raise _wm._OptionError(f"invalid regular expression for "
f"module: {module!r}") from None
# Should never happen.
raise
# Helper for _setoption() # Helper for _setoption()
@@ -449,9 +471,12 @@ def warn(message, category=None, stacklevel=1, source=None,
# Check category argument # Check category argument
if category is None: if category is None:
category = UserWarning category = UserWarning
if not (isinstance(category, type) and issubclass(category, Warning)): elif not isinstance(category, type):
raise TypeError("category must be a Warning subclass, " raise TypeError(f"category must be a Warning subclass, not "
"not '{:s}'".format(type(category).__name__)) f"'{type(category).__name__}'")
elif not issubclass(category, Warning):
raise TypeError(f"category must be a Warning subclass, not "
f"class '{category.__name__}'")
if not isinstance(skip_file_prefixes, tuple): if not isinstance(skip_file_prefixes, tuple):
# The C version demands a tuple for implementation performance. # The C version demands a tuple for implementation performance.
raise TypeError('skip_file_prefixes must be a tuple of strs.') raise TypeError('skip_file_prefixes must be a tuple of strs.')
@@ -495,14 +520,43 @@ def warn(message, category=None, stacklevel=1, source=None,
) )
def _match_filename(pattern, filename, *, MS_WINDOWS=(sys.platform == 'win32')):
if not filename:
return pattern.match('<unknown>') is not None
if filename[0] == '<' and filename[-1] == '>':
return pattern.match(filename) is not None
is_py = (filename[-3:].lower() == '.py'
if MS_WINDOWS else
filename.endswith('.py'))
if is_py:
filename = filename[:-3]
if pattern.match(filename): # for backward compatibility
return True
if MS_WINDOWS:
if not is_py and filename[-4:].lower() == '.pyw':
filename = filename[:-4]
is_py = True
if is_py and filename[-9:].lower() in (r'\__init__', '/__init__'):
filename = filename[:-9]
filename = filename.replace('\\', '/')
else:
if is_py and filename.endswith('/__init__'):
filename = filename[:-9]
filename = filename.replace('/', '.')
i = 0
while True:
if pattern.match(filename, i):
return True
i = filename.find('.', i) + 1
if not i:
return False
def warn_explicit(message, category, filename, lineno, def warn_explicit(message, category, filename, lineno,
module=None, registry=None, module_globals=None, module=None, registry=None, module_globals=None,
source=None): source=None):
lineno = int(lineno) lineno = int(lineno)
if module is None:
module = filename or "<unknown>"
if module[-3:].lower() == ".py":
module = module[:-3] # XXX What about leading pathname?
if isinstance(message, Warning): if isinstance(message, Warning):
text = str(message) text = str(message)
category = message.__class__ category = message.__class__
@@ -524,9 +578,11 @@ def warn_explicit(message, category, filename, lineno,
action, msg, cat, mod, ln = item action, msg, cat, mod, ln = item
if ((msg is None or msg.match(text)) and if ((msg is None or msg.match(text)) and
issubclass(category, cat) and issubclass(category, cat) and
(mod is None or mod.match(module)) and (ln == 0 or lineno == ln) and
(ln == 0 or lineno == ln)): (mod is None or (_match_filename(mod, filename)
break if module is None else
mod.match(module)))):
break
else: else:
action = _wm.defaultaction action = _wm.defaultaction
# Early exit actions # Early exit actions
@@ -564,17 +620,18 @@ def warn_explicit(message, category, filename, lineno,
linecache.getlines(filename, module_globals) linecache.getlines(filename, module_globals)
# Print message and context # Print message and context
msg = _wm.WarningMessage(message, category, filename, lineno, source=source) msg = _wm.WarningMessage(message, category, filename, lineno,
module=module, source=source)
_wm._showwarnmsg(msg) _wm._showwarnmsg(msg)
class WarningMessage(object): class WarningMessage(object):
_WARNING_DETAILS = ("message", "category", "filename", "lineno", "file", _WARNING_DETAILS = ("message", "category", "filename", "lineno", "file",
"line", "source") "line", "source", "module")
def __init__(self, message, category, filename, lineno, file=None, def __init__(self, message, category, filename, lineno, file=None,
line=None, source=None): line=None, source=None, module=None):
self.message = message self.message = message
self.category = category self.category = category
self.filename = filename self.filename = filename
@@ -582,12 +639,14 @@ class WarningMessage(object):
self.file = file self.file = file
self.line = line self.line = line
self.source = source self.source = source
self.module = module
self._category_name = category.__name__ if category else None self._category_name = category.__name__ if category else None
def __str__(self): def __str__(self):
return ("{message : %r, category : %r, filename : %r, lineno : %s, " return ("{message : %r, category : %r, module : %r, "
"line : %r}" % (self.message, self._category_name, "filename : %r, lineno : %s, line : %r}" % (
self.filename, self.lineno, self.line)) self.message, self._category_name, self.module,
self.filename, self.lineno, self.line))
def __repr__(self): def __repr__(self):
return f'<{type(self).__qualname__} {self}>' return f'<{type(self).__qualname__} {self}>'
@@ -647,8 +706,8 @@ class catch_warnings(object):
context = None context = None
self._filters = self._module.filters self._filters = self._module.filters
self._module.filters = self._filters[:] self._module.filters = self._filters[:]
self._showwarning = self._module.showwarning
self._showwarnmsg_impl = self._module._showwarnmsg_impl self._showwarnmsg_impl = self._module._showwarnmsg_impl
self._showwarning = self._module.showwarning
self._module._filters_mutated_lock_held() self._module._filters_mutated_lock_held()
if self._record: if self._record:
if _use_context: if _use_context:
@@ -656,9 +715,9 @@ class catch_warnings(object):
else: else:
log = [] log = []
self._module._showwarnmsg_impl = log.append self._module._showwarnmsg_impl = log.append
# Reset showwarning() to the default implementation to make sure # Reset showwarning() to the default implementation to make sure
# that _showwarnmsg() calls _showwarnmsg_impl() # that _showwarnmsg() calls _showwarnmsg_impl()
self._module.showwarning = self._module._showwarning_orig self._module.showwarning = self._module._showwarning_orig
else: else:
log = None log = None
if self._filter is not None: if self._filter is not None:
@@ -673,8 +732,8 @@ class catch_warnings(object):
self._module._warnings_context.set(self._saved_context) self._module._warnings_context.set(self._saved_context)
else: else:
self._module.filters = self._filters self._module.filters = self._filters
self._module.showwarning = self._showwarning
self._module._showwarnmsg_impl = self._showwarnmsg_impl self._module._showwarnmsg_impl = self._showwarnmsg_impl
self._module.showwarning = self._showwarning
self._module._filters_mutated_lock_held() self._module._filters_mutated_lock_held()
+50 -22
View File
@@ -2251,14 +2251,17 @@ impl Blocks {
AnyInstruction::Real(Instruction::CallIntrinsic1 { func }) => { AnyInstruction::Real(Instruction::CallIntrinsic1 { func }) => {
match func.get(inst.arg) { match func.get(inst.arg) {
IntrinsicFunction1::ListToTuple => { IntrinsicFunction1::ListToTuple => {
if matches!(nextop, Some(Instruction::GetIter { .. })) { fold_constant_sequence_into_load_const(
metadata,
&mut self[block_idx],
i,
)?;
if matches!(
self[block_idx].instructions[i].instr.real(),
Some(Instruction::CallIntrinsic1 { .. })
) && matches!(nextop, Some(Instruction::GetIter { .. }))
{
self[block_idx].instructions[i].set_to_nop(); self[block_idx].instructions[i].set_to_nop();
} else {
fold_constant_intrinsic_list_to_tuple(
metadata,
&mut self[block_idx],
i,
)?;
} }
} }
IntrinsicFunction1::UnaryPositive => { IntrinsicFunction1::UnaryPositive => {
@@ -2267,6 +2270,16 @@ impl Blocks {
_ => {} _ => {}
} }
} }
AnyInstruction::Real(
Instruction::ListAppend { .. } | Instruction::SetAdd { .. },
) if u32::from(inst.arg) == 1
&& matches!(
nextop,
Some(Instruction::GetIter { .. } | Instruction::ContainsOp { .. })
) =>
{
fold_constant_sequence_into_load_const(metadata, &mut self[block_idx], i)?;
}
AnyInstruction::Real(Instruction::BinaryOp { .. }) => { AnyInstruction::Real(Instruction::BinaryOp { .. }) => {
fold_const_binop(metadata, &mut self[block_idx], i)?; fold_const_binop(metadata, &mut self[block_idx], i)?;
} }
@@ -5456,20 +5469,24 @@ fn fold_tuple_of_constants(
Ok(true) Ok(true)
} }
fn fold_constant_intrinsic_list_to_tuple( fn fold_constant_sequence_into_load_const(
metadata: &mut CodeUnitMetadata, metadata: &mut CodeUnitMetadata,
block: &mut Block, block: &mut Block,
i: usize, i: usize,
) -> crate::InternalResult<bool> { ) -> crate::InternalResult<bool> {
let Some(Instruction::CallIntrinsic1 { func }) = block.instructions[i].instr.real() else { let (intrinsic, is_set) = match block.instructions[i].instr.real() {
return Ok(false); Some(Instruction::CallIntrinsic1 { func })
if func.get(block.instructions[i].arg) == IntrinsicFunction1::ListToTuple =>
{
(true, false)
}
Some(Instruction::ListAppend { .. }) => (false, false),
Some(Instruction::SetAdd { .. }) => (false, true),
_ => return Ok(false),
}; };
if func.get(block.instructions[i].arg) != IntrinsicFunction1::ListToTuple {
return Ok(false);
}
let mut consts_found = 0usize; let mut consts_found = 0usize;
let mut expect_append = true; let mut expect_append = intrinsic;
let mut pos = i; let mut pos = i;
while let Some(prev) = pos.checked_sub(1) { while let Some(prev) = pos.checked_sub(1) {
pos = prev; pos = prev;
@@ -5478,9 +5495,12 @@ fn fold_constant_intrinsic_list_to_tuple(
continue; continue;
} }
if matches!(instr.instr.real(), Some(Instruction::BuildList { .. })) let is_start = if is_set {
&& u32::from(instr.arg) == 0 matches!(instr.instr.real(), Some(Instruction::BuildSet { .. }))
{ } else {
matches!(instr.instr.real(), Some(Instruction::BuildList { .. }))
};
if is_start && u32::from(instr.arg) == 0 {
if !expect_append { if !expect_append {
return Ok(false); return Ok(false);
} }
@@ -5489,7 +5509,8 @@ fn fold_constant_intrinsic_list_to_tuple(
elements elements
.try_reserve_exact(consts_found) .try_reserve_exact(consts_found)
.map_err(|_| InternalError::MalformedControlFlowGraph)?; .map_err(|_| InternalError::MalformedControlFlowGraph)?;
for idx in (pos..i).rev() { let end = if intrinsic { i } else { i + 1 };
for idx in (pos..end).rev() {
if matches!(block.instructions[idx].instr.real(), Some(Instruction::Nop)) { if matches!(block.instructions[idx].instr.real(), Some(Instruction::Nop)) {
continue; continue;
} }
@@ -5506,15 +5527,22 @@ fn fold_constant_intrinsic_list_to_tuple(
instr_make_load_const( instr_make_load_const(
metadata, metadata,
&mut block.instructions[i], &mut block.instructions[i],
ConstantData::Tuple { elements }, if is_set {
ConstantData::Frozenset { elements }
} else {
ConstantData::Tuple { elements }
},
)?; )?;
return Ok(true); return Ok(true);
} }
if expect_append { if expect_append {
if !matches!(instr.instr.real(), Some(Instruction::ListAppend { .. })) let is_append = if is_set {
|| u32::from(instr.arg) != 1 matches!(instr.instr.real(), Some(Instruction::SetAdd { .. }))
{ } else {
matches!(instr.instr.real(), Some(Instruction::ListAppend { .. }))
};
if !is_append || u32::from(instr.arg) != 1 {
return Ok(false); return Ok(false);
} }
} else { } else {
+28 -6
View File
@@ -20,6 +20,7 @@ pub enum CFormatErrorType {
MissingModuloSign, MissingModuloSign,
UnsupportedFormatChar(CodePoint), UnsupportedFormatChar(CodePoint),
IncompleteFormat, IncompleteFormat,
StrayModulo,
WidthTooBig, WidthTooBig,
PrecisionTooBig, PrecisionTooBig,
// Unimplemented, // Unimplemented,
@@ -39,6 +40,7 @@ impl fmt::Display for CFormatError {
match self.typ { match self.typ {
CFormatErrorType::UnmatchedKeyParentheses => write!(f, "incomplete format key"), CFormatErrorType::UnmatchedKeyParentheses => write!(f, "incomplete format key"),
CFormatErrorType::IncompleteFormat => write!(f, "incomplete format"), CFormatErrorType::IncompleteFormat => write!(f, "incomplete format"),
CFormatErrorType::StrayModulo => write!(f, "stray % at position {}", self.index),
CFormatErrorType::UnsupportedFormatChar(c) => write!( CFormatErrorType::UnsupportedFormatChar(c) => write!(
f, f,
"unsupported format character '{}' ({:#x}) at index {}", "unsupported format character '{}' ({:#x}) at index {}",
@@ -832,8 +834,20 @@ impl<S> CFormatStrOrBytes<S> {
let spec = let spec =
CFormatSpecKeyed::parse(iter, context).map_err(|err| CFormatError { CFormatSpecKeyed::parse(iter, context).map_err(|err| CFormatError {
typ: err.0, typ: if context == CFormatContext::Str
index: err.1, && err.0 == CFormatErrorType::IncompleteFormat
{
CFormatErrorType::StrayModulo
} else {
err.0
},
index: if context == CFormatContext::Str
&& err.0 == CFormatErrorType::IncompleteFormat
{
index
} else {
err.1
},
})?; })?;
parts.push((index, CFormatPart::Spec(spec))); parts.push((index, CFormatPart::Spec(spec)));
@@ -842,8 +856,16 @@ impl<S> CFormatStrOrBytes<S> {
} }
} else { } else {
return Err(CFormatError { return Err(CFormatError {
typ: CFormatErrorType::IncompleteFormat, typ: if context == CFormatContext::Str {
index: index + 1, CFormatErrorType::StrayModulo
} else {
CFormatErrorType::IncompleteFormat
},
index: if context == CFormatContext::Str {
index
} else {
index + 1
},
}); });
} }
} else { } else {
@@ -991,8 +1013,8 @@ mod tests {
assert_eq!( assert_eq!(
"Hello %".parse::<CFormatString>(), "Hello %".parse::<CFormatString>(),
Err(CFormatError { Err(CFormatError {
typ: CFormatErrorType::IncompleteFormat, typ: CFormatErrorType::StrayModulo,
index: 7 index: 6
}) })
); );
} }
+28 -5
View File
@@ -427,7 +427,6 @@ pub struct PyCode {
#[pymember(name = "co_posonlyargcount", path = "posonlyarg_count")] #[pymember(name = "co_posonlyargcount", path = "posonlyarg_count")]
#[pymember(name = "co_kwonlyargcount", path = "kwonlyarg_count")] #[pymember(name = "co_kwonlyargcount", path = "kwonlyarg_count")]
#[pymember(name = "co_stacksize", path = "max_stackdepth")] #[pymember(name = "co_stacksize", path = "max_stackdepth")]
#[pymember(name = "co_name", path = "obj_name")]
#[pymember(name = "co_qualname", path = "qualname")] #[pymember(name = "co_qualname", path = "qualname")]
#[pymember(name = "co_flags", path = "flags")] #[pymember(name = "co_flags", path = "flags")]
// Logical code fields are finalized by `new`. Execution may specialize or // Logical code fields are finalized by `new`. Execution may specialize or
@@ -436,6 +435,8 @@ pub struct PyCode {
pub code: CodeObject, pub code: CodeObject,
/// Canonical bytes captured before execution can instrument the code. /// Canonical bytes captured before execution can instrument the code.
canonical_code: Box<[u8]>, canonical_code: Box<[u8]>,
/// Preserve a public co_name subclass; exact strings use the interned owner.
name_owner: Option<PyStrRef>,
/// The stable Python object exposed by co_code, allocated on first access. /// The stable Python object exposed by co_code, allocated on first access.
code_bytes: OnceCell<PyBytesRef>, code_bytes: OnceCell<PyBytesRef>,
/// Slot-indexed names, equivalent to CPython's `co_localsplusnames`. /// Slot-indexed names, equivalent to CPython's `co_localsplusnames`.
@@ -578,7 +579,7 @@ impl PyCode {
CodeField::Names => self.names_tuple.as_object(), CodeField::Names => self.names_tuple.as_object(),
CodeField::LocalsPlusNames => self.localsplus_names_tuple.as_object(), CodeField::LocalsPlusNames => self.localsplus_names_tuple.as_object(),
CodeField::LocalsPlusKinds => self.localsplus_kinds_bytes.as_object(), CodeField::LocalsPlusKinds => self.localsplus_kinds_bytes.as_object(),
CodeField::Name => self.code.obj_name.as_object(), CodeField::Name => self.name_object(),
CodeField::Qualname => self.code.qualname.as_object(), CodeField::Qualname => self.code.qualname.as_object(),
CodeField::LineTable => self.linetable_bytes.as_object(), CodeField::LineTable => self.linetable_bytes.as_object(),
CodeField::ExceptionTable => self.exceptiontable_bytes.as_object(), CodeField::ExceptionTable => self.exceptiontable_bytes.as_object(),
@@ -661,6 +662,7 @@ impl PyCode {
cellvars_tuple: OnceCell::new(), cellvars_tuple: OnceCell::new(),
freevars_tuple: OnceCell::new(), freevars_tuple: OnceCell::new(),
canonical_code, canonical_code,
name_owner: None,
code_bytes: OnceCell::new(), code_bytes: OnceCell::new(),
localsplus_names, localsplus_names,
source_path: AtomicPtr::new(sp), source_path: AtomicPtr::new(sp),
@@ -739,6 +741,18 @@ impl PyCode {
self.localsplus_names[index] self.localsplus_names[index]
} }
fn name_object(&self) -> &PyObject {
self.name_owner
.as_ref()
.map_or_else(|| self.code.obj_name.as_object(), |name| name.as_object())
}
fn retain_name_subclass(&mut self, name: PyStrRef, ctx: &Context) {
if !name.class().is(ctx.types.str_type) {
self.name_owner = Some(name);
}
}
pub fn source_path(&self) -> &'static PyStrInterned { pub fn source_path(&self) -> &'static PyStrInterned {
// SAFETY: always points to a valid &'static PyStrInterned (interned strings are never deallocated) // SAFETY: always points to a valid &'static PyStrInterned (interned strings are never deallocated)
unsafe { &*self.source_path.load(Ordering::Relaxed) } unsafe { &*self.source_path.load(Ordering::Relaxed) }
@@ -912,7 +926,7 @@ impl Comparable for PyCode {
let other = class_or_notimplemented!(Self, other); let other = class_or_notimplemented!(Self, other);
let a = &zelf.code; let a = &zelf.code;
let b = &other.code; let b = &other.code;
let eq = a.obj_name == b.obj_name let eq = vm.bool_eq(zelf.name_object(), other.name_object())?
&& a.arg_count == b.arg_count && a.arg_count == b.arg_count
&& a.posonlyarg_count == b.posonlyarg_count && a.posonlyarg_count == b.posonlyarg_count
&& a.kwonlyarg_count == b.kwonlyarg_count && a.kwonlyarg_count == b.kwonlyarg_count
@@ -951,7 +965,7 @@ impl Hashable for PyCode {
let code = &zelf.code; let code = &zelf.code;
// Hash a tuple of key attributes, matching CPython's code_hash // Hash a tuple of key attributes, matching CPython's code_hash
let tuple = vm.ctx.new_tuple(vec![ let tuple = vm.ctx.new_tuple(vec![
vm.ctx.new_str(code.obj_name.as_str()).into(), zelf.name_object().to_owned(),
vm.ctx.new_int(code.arg_count).into(), vm.ctx.new_int(code.arg_count).into(),
vm.ctx.new_int(code.posonlyarg_count).into(), vm.ctx.new_int(code.posonlyarg_count).into(),
vm.ctx.new_int(code.kwonlyarg_count).into(), vm.ctx.new_int(code.kwonlyarg_count).into(),
@@ -1127,6 +1141,7 @@ impl Constructor for PyCode {
}; };
let mut result = Self::new(code, &vm.ctx); let mut result = Self::new(code, &vm.ctx);
result.retain_name_subclass(args.name, &vm.ctx);
result.linetable_bytes = args.linetable; result.linetable_bytes = args.linetable;
result.exceptiontable_bytes = args.exceptiontable; result.exceptiontable_bytes = args.exceptiontable;
Ok(result) Ok(result)
@@ -1160,6 +1175,13 @@ impl Py<PyCode> {
self.code.first_line_number.map_or(0, |n| n.get() as _) self.code.first_line_number.map_or(0, |n| n.get() as _)
} }
#[pygetset]
fn co_name(&self) -> PyStrRef {
self.name_owner
.clone()
.unwrap_or_else(|| self.code.obj_name.to_owned())
}
#[pygetset] #[pygetset]
fn co_consts(&self) -> PyTupleRef { fn co_consts(&self) -> PyTupleRef {
self.code self.code
@@ -1606,7 +1628,7 @@ impl Py<PyCode> {
let obj_name = match co_name { let obj_name = match co_name {
OptionalArg::Present(obj_name) => obj_name, OptionalArg::Present(obj_name) => obj_name,
OptionalArg::Missing => self.code.obj_name.to_owned(), OptionalArg::Missing => self.co_name(),
}; };
let names = match co_names { let names = match co_names {
@@ -1735,6 +1757,7 @@ impl Py<PyCode> {
}; };
let mut result = PyCode::new(new_code, &vm.ctx); let mut result = PyCode::new(new_code, &vm.ctx);
result.retain_name_subclass(obj_name, &vm.ctx);
result.retain_normalized_names(names, None); result.retain_normalized_names(names, None);
result.linetable_bytes = linetable; result.linetable_bytes = linetable;
result.exceptiontable_bytes = exceptiontable; result.exceptiontable_bytes = exceptiontable;
+26 -6
View File
@@ -203,8 +203,14 @@ fn spec_format_string(
spec: &CFormatSpec, spec: &CFormatSpec,
obj: PyObjectRef, obj: PyObjectRef,
argument: FormatArgument<'_>, argument: FormatArgument<'_>,
format_start: usize,
) -> PyResult<Wtf8Buf> { ) -> PyResult<Wtf8Buf> {
match &spec.format_type { match &spec.format_type {
CFormatType::Unsupported { ch, .. } if ch.to_char_lossy().is_ascii_alphabetic() => Err(vm
.new_value_error(format!(
"unsupported format %{} at position {format_start}",
ch.to_char_lossy(),
))),
CFormatType::Unsupported { ch, index } => Err(vm.new_value_error(format!( CFormatType::Unsupported { ch, index } => Err(vm.new_value_error(format!(
"unsupported format character '{}' ({:#x}) at index {index}", "unsupported format character '{}' ({:#x}) at index {index}",
ch.to_char_lossy(), ch.to_char_lossy(),
@@ -245,15 +251,27 @@ fn spec_format_string(
Ok(spec.format_number(i.as_bigint()).into()) Ok(spec.format_number(i.as_bigint()).into())
} else { } else {
Err(vm.new_type_error(format!( Err(vm.new_type_error(format!(
"%{} format: an integer is required, not {}", "{}: %{} requires an integer, not {}",
argument.description(vm)?,
spec.format_type.to_char(), spec.format_type.to_char(),
obj.class().name() obj.class().slot_name()
))) )))
} }
} }
}, },
CFormatType::Float(_) => { CFormatType::Float(_) => {
let value = ArgIntoFloat::try_from_object(vm, obj)?; let value = match ArgIntoFloat::try_from_object(vm, obj.clone()) {
Ok(value) => value,
Err(error) if error.fast_isinstance(vm.ctx.exceptions.type_error) => {
return Err(vm.new_type_error(format!(
"{}: %{} requires a real number, not {}",
argument.description(vm)?,
spec.format_type.to_char(),
obj.class().slot_name()
)));
}
Err(error) => return Err(error),
};
Ok(spec.format_float(value.into()).into()) Ok(spec.format_float(value.into()).into())
} }
CFormatType::Character(CCharacterType::Character) => { CFormatType::Character(CCharacterType::Character) => {
@@ -504,7 +522,7 @@ pub(crate) fn cformat_string(
} }
// dict // dict
for (_, part) in format { for (format_start, part) in format {
match part { match part {
CFormatPart::Literal(literal) => result.push_wtf8(&literal), CFormatPart::Literal(literal) => result.push_wtf8(&literal),
CFormatPart::Spec(CFormatSpecKeyed { mapping_key, spec }) => { CFormatPart::Spec(CFormatSpecKeyed { mapping_key, spec }) => {
@@ -516,6 +534,7 @@ pub(crate) fn cformat_string(
&spec, &spec,
value, value,
FormatArgument::Mapping(key.as_object()), FormatArgument::Mapping(key.as_object()),
format_start,
)?; )?;
result.push_wtf8(&part_result); result.push_wtf8(&part_result);
} }
@@ -540,7 +559,7 @@ pub(crate) fn cformat_string(
let mut value_iter = value_iter.inspect(|_| argument_index.set(argument_index.get() + 1)); let mut value_iter = value_iter.inspect(|_| argument_index.set(argument_index.get() + 1));
let is_tuple = values_obj.downcast_ref::<tuple::PyTuple>().is_some(); let is_tuple = values_obj.downcast_ref::<tuple::PyTuple>().is_some();
for (_, part) in format { for (format_start, part) in format {
match part { match part {
CFormatPart::Literal(literal) => result.push_wtf8(&literal), CFormatPart::Literal(literal) => result.push_wtf8(&literal),
CFormatPart::Spec(CFormatSpecKeyed { mut spec, .. }) => { CFormatPart::Spec(CFormatSpecKeyed { mut spec, .. }) => {
@@ -561,7 +580,8 @@ pub(crate) fn cformat_string(
} else { } else {
FormatArgument::Scalar FormatArgument::Scalar
}; };
let part_result = spec_format_string(vm, &spec, value.to_owned(), argument)?; let part_result =
spec_format_string(vm, &spec, value.to_owned(), argument, format_start)?;
result.push_wtf8(&part_result); result.push_wtf8(&part_result);
} }
} }
+42 -15
View File
@@ -336,22 +336,38 @@ fn update_registry(
already_warned(registry, &altkey, true, vm) already_warned(registry, &altkey, true, vm)
} }
fn normalize_module(filename: &Py<PyStr>, vm: &VirtualMachine) -> PyObjectRef { fn check_module_matched(
match filename.byte_len() { pattern: &PyObject,
0 => vm.new_pyobj("<unknown>"), module: Option<&PyObject>,
len if len >= 3 && filename.as_bytes().ends_with(b".py") => { filename: &Py<PyStr>,
vm.new_pyobj(&filename.as_wtf8()[..len - 3]) vm: &VirtualMachine,
} ) -> PyResult<bool> {
_ => filename.as_object().to_owned(), if let Some(module) = module {
return check_matched(pattern, module, vm);
} }
if vm.is_none(pattern) {
return Ok(true);
}
if pattern.class().is(vm.ctx.types.str_type) {
return Ok(false);
}
let warnings = vm.import("_py_warnings", 0)?;
vm.call_method(
&warnings,
"_match_filename",
(pattern.to_owned(), filename.to_owned()),
)?
.is_true(vm)
} }
/// Search a filters list for a matching action. /// Search a filters list for a matching action.
#[allow(clippy::too_many_arguments)]
fn filter_search( fn filter_search(
category: &PyObject, category: &PyObject,
text: &PyObject, text: &PyObject,
lineno: usize, lineno: usize,
module: &PyObject, module: Option<&PyObject>,
filename: &Py<PyStr>,
filters: &PyListRef, filters: &PyListRef,
list_name: &str, list_name: &str,
vm: &VirtualMachine, vm: &VirtualMachine,
@@ -380,7 +396,7 @@ fn filter_search(
} }
let good_msg = check_matched(&tmp_item.as_slice()[1], text, vm)?; let good_msg = check_matched(&tmp_item.as_slice()[1], text, vm)?;
let is_subclass = category.is_subclass(&tmp_item.as_slice()[2], vm)?; let is_subclass = category.is_subclass(&tmp_item.as_slice()[2], vm)?;
let good_mod = check_matched(&tmp_item.as_slice()[3], module, vm)?; let good_mod = check_module_matched(&tmp_item.as_slice()[3], module, filename, vm)?;
let ln: usize = tmp_item.as_slice()[4] let ln: usize = tmp_item.as_slice()[4]
.try_int(vm) .try_int(vm)
.map_or(0, |v| v.as_u32_mask() as _); .map_or(0, |v| v.as_u32_mask() as _);
@@ -397,7 +413,8 @@ fn get_filter(
category: &PyObject, category: &PyObject,
text: &PyObject, text: &PyObject,
lineno: usize, lineno: usize,
module: &PyObject, module: Option<&PyObject>,
filename: &Py<PyStr>,
vm: &VirtualMachine, vm: &VirtualMachine,
) -> PyResult { ) -> PyResult {
if let Some(context_filters) = get_warnings_context_filters(vm)? { if let Some(context_filters) = get_warnings_context_filters(vm)? {
@@ -406,6 +423,7 @@ fn get_filter(
text, text,
lineno, lineno,
module, module,
filename,
&context_filters, &context_filters,
"_warnings_context _filters", "_warnings_context _filters",
vm, vm,
@@ -416,7 +434,9 @@ fn get_filter(
} }
let filters = get_warnings_filters(vm)?; let filters = get_warnings_filters(vm)?;
if let Some(action) = filter_search(category, text, lineno, module, &filters, "filters", vm)? { if let Some(action) = filter_search(
category, text, lineno, module, filename, &filters, "filters", vm,
)? {
return Ok(action); return Ok(action);
} }
get_default_action(vm) get_default_action(vm)
@@ -504,9 +524,9 @@ pub fn warn_explicit(
source: Option<PyObjectRef>, source: Option<PyObjectRef>,
vm: &VirtualMachine, vm: &VirtualMachine,
) -> PyResult<()> { ) -> PyResult<()> {
// Normalize module. None → silent return (late-shutdown safety). // Explicit None remains a late-shutdown silent return. An omitted module
let module = module.unwrap_or_else(|| normalize_module(&filename, vm)); // is matched against the filename by the canonical warnings helper.
if vm.is_none(&module) { if module.as_ref().is_some_and(|module| vm.is_none(module)) {
return Ok(()); return Ok(());
} }
@@ -543,7 +563,14 @@ pub fn warn_explicit(
} }
// Get filter action // Get filter action
let action = get_filter(category.as_object(), text.as_object(), lineno, &module, vm)?; let action = get_filter(
category.as_object(),
text.as_object(),
lineno,
module.as_deref(),
&filename,
vm,
)?;
let action_str = PyStrRef::try_from_object(vm, action) let action_str = PyStrRef::try_from_object(vm, action)
.map_err(|_| vm.new_type_error("action must be a string"))?; .map_err(|_| vm.new_type_error("action must be a string"))?;