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:
import re
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:
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:
try:
lineno = int(lineno)
@@ -381,7 +387,23 @@ def _setoption(arg):
raise _wm._OptionError("invalid lineno %r" % (lineno,)) from None
else:
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()
@@ -449,9 +471,12 @@ def warn(message, category=None, stacklevel=1, source=None,
# Check category argument
if category is None:
category = UserWarning
if not (isinstance(category, type) and issubclass(category, Warning)):
raise TypeError("category must be a Warning subclass, "
"not '{:s}'".format(type(category).__name__))
elif not isinstance(category, type):
raise TypeError(f"category must be a Warning subclass, not "
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):
# The C version demands a tuple for implementation performance.
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,
module=None, registry=None, module_globals=None,
source=None):
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):
text = str(message)
category = message.__class__
@@ -524,9 +578,11 @@ def warn_explicit(message, category, filename, lineno,
action, msg, cat, mod, ln = item
if ((msg is None or msg.match(text)) and
issubclass(category, cat) and
(mod is None or mod.match(module)) and
(ln == 0 or lineno == ln)):
break
(ln == 0 or lineno == ln) and
(mod is None or (_match_filename(mod, filename)
if module is None else
mod.match(module)))):
break
else:
action = _wm.defaultaction
# Early exit actions
@@ -564,17 +620,18 @@ def warn_explicit(message, category, filename, lineno,
linecache.getlines(filename, module_globals)
# 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)
class WarningMessage(object):
_WARNING_DETAILS = ("message", "category", "filename", "lineno", "file",
"line", "source")
"line", "source", "module")
def __init__(self, message, category, filename, lineno, file=None,
line=None, source=None):
line=None, source=None, module=None):
self.message = message
self.category = category
self.filename = filename
@@ -582,12 +639,14 @@ class WarningMessage(object):
self.file = file
self.line = line
self.source = source
self.module = module
self._category_name = category.__name__ if category else None
def __str__(self):
return ("{message : %r, category : %r, filename : %r, lineno : %s, "
"line : %r}" % (self.message, self._category_name,
self.filename, self.lineno, self.line))
return ("{message : %r, category : %r, module : %r, "
"filename : %r, lineno : %s, line : %r}" % (
self.message, self._category_name, self.module,
self.filename, self.lineno, self.line))
def __repr__(self):
return f'<{type(self).__qualname__} {self}>'
@@ -647,8 +706,8 @@ class catch_warnings(object):
context = None
self._filters = self._module.filters
self._module.filters = self._filters[:]
self._showwarning = self._module.showwarning
self._showwarnmsg_impl = self._module._showwarnmsg_impl
self._showwarning = self._module.showwarning
self._module._filters_mutated_lock_held()
if self._record:
if _use_context:
@@ -656,9 +715,9 @@ class catch_warnings(object):
else:
log = []
self._module._showwarnmsg_impl = log.append
# Reset showwarning() to the default implementation to make sure
# that _showwarnmsg() calls _showwarnmsg_impl()
self._module.showwarning = self._module._showwarning_orig
# Reset showwarning() to the default implementation to make sure
# that _showwarnmsg() calls _showwarnmsg_impl()
self._module.showwarning = self._module._showwarning_orig
else:
log = None
if self._filter is not None:
@@ -673,8 +732,8 @@ class catch_warnings(object):
self._module._warnings_context.set(self._saved_context)
else:
self._module.filters = self._filters
self._module.showwarning = self._showwarning
self._module._showwarnmsg_impl = self._showwarnmsg_impl
self._module.showwarning = self._showwarning
self._module._filters_mutated_lock_held()
+50 -22
View File
@@ -2251,14 +2251,17 @@ impl Blocks {
AnyInstruction::Real(Instruction::CallIntrinsic1 { func }) => {
match func.get(inst.arg) {
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();
} else {
fold_constant_intrinsic_list_to_tuple(
metadata,
&mut self[block_idx],
i,
)?;
}
}
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 { .. }) => {
fold_const_binop(metadata, &mut self[block_idx], i)?;
}
@@ -5456,20 +5469,24 @@ fn fold_tuple_of_constants(
Ok(true)
}
fn fold_constant_intrinsic_list_to_tuple(
fn fold_constant_sequence_into_load_const(
metadata: &mut CodeUnitMetadata,
block: &mut Block,
i: usize,
) -> crate::InternalResult<bool> {
let Some(Instruction::CallIntrinsic1 { func }) = block.instructions[i].instr.real() else {
return Ok(false);
let (intrinsic, is_set) = match block.instructions[i].instr.real() {
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 expect_append = true;
let mut expect_append = intrinsic;
let mut pos = i;
while let Some(prev) = pos.checked_sub(1) {
pos = prev;
@@ -5478,9 +5495,12 @@ fn fold_constant_intrinsic_list_to_tuple(
continue;
}
if matches!(instr.instr.real(), Some(Instruction::BuildList { .. }))
&& u32::from(instr.arg) == 0
{
let is_start = if is_set {
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 {
return Ok(false);
}
@@ -5489,7 +5509,8 @@ fn fold_constant_intrinsic_list_to_tuple(
elements
.try_reserve_exact(consts_found)
.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)) {
continue;
}
@@ -5506,15 +5527,22 @@ fn fold_constant_intrinsic_list_to_tuple(
instr_make_load_const(
metadata,
&mut block.instructions[i],
ConstantData::Tuple { elements },
if is_set {
ConstantData::Frozenset { elements }
} else {
ConstantData::Tuple { elements }
},
)?;
return Ok(true);
}
if expect_append {
if !matches!(instr.instr.real(), Some(Instruction::ListAppend { .. }))
|| u32::from(instr.arg) != 1
{
let is_append = if is_set {
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);
}
} else {
+28 -6
View File
@@ -20,6 +20,7 @@ pub enum CFormatErrorType {
MissingModuloSign,
UnsupportedFormatChar(CodePoint),
IncompleteFormat,
StrayModulo,
WidthTooBig,
PrecisionTooBig,
// Unimplemented,
@@ -39,6 +40,7 @@ impl fmt::Display for CFormatError {
match self.typ {
CFormatErrorType::UnmatchedKeyParentheses => write!(f, "incomplete format key"),
CFormatErrorType::IncompleteFormat => write!(f, "incomplete format"),
CFormatErrorType::StrayModulo => write!(f, "stray % at position {}", self.index),
CFormatErrorType::UnsupportedFormatChar(c) => write!(
f,
"unsupported format character '{}' ({:#x}) at index {}",
@@ -832,8 +834,20 @@ impl<S> CFormatStrOrBytes<S> {
let spec =
CFormatSpecKeyed::parse(iter, context).map_err(|err| CFormatError {
typ: err.0,
index: err.1,
typ: if context == CFormatContext::Str
&& 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)));
@@ -842,8 +856,16 @@ impl<S> CFormatStrOrBytes<S> {
}
} else {
return Err(CFormatError {
typ: CFormatErrorType::IncompleteFormat,
index: index + 1,
typ: if context == CFormatContext::Str {
CFormatErrorType::StrayModulo
} else {
CFormatErrorType::IncompleteFormat
},
index: if context == CFormatContext::Str {
index
} else {
index + 1
},
});
}
} else {
@@ -991,8 +1013,8 @@ mod tests {
assert_eq!(
"Hello %".parse::<CFormatString>(),
Err(CFormatError {
typ: CFormatErrorType::IncompleteFormat,
index: 7
typ: CFormatErrorType::StrayModulo,
index: 6
})
);
}
+28 -5
View File
@@ -427,7 +427,6 @@ pub struct PyCode {
#[pymember(name = "co_posonlyargcount", path = "posonlyarg_count")]
#[pymember(name = "co_kwonlyargcount", path = "kwonlyarg_count")]
#[pymember(name = "co_stacksize", path = "max_stackdepth")]
#[pymember(name = "co_name", path = "obj_name")]
#[pymember(name = "co_qualname", path = "qualname")]
#[pymember(name = "co_flags", path = "flags")]
// Logical code fields are finalized by `new`. Execution may specialize or
@@ -436,6 +435,8 @@ pub struct PyCode {
pub code: CodeObject,
/// Canonical bytes captured before execution can instrument the code.
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.
code_bytes: OnceCell<PyBytesRef>,
/// Slot-indexed names, equivalent to CPython's `co_localsplusnames`.
@@ -578,7 +579,7 @@ impl PyCode {
CodeField::Names => self.names_tuple.as_object(),
CodeField::LocalsPlusNames => self.localsplus_names_tuple.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::LineTable => self.linetable_bytes.as_object(),
CodeField::ExceptionTable => self.exceptiontable_bytes.as_object(),
@@ -661,6 +662,7 @@ impl PyCode {
cellvars_tuple: OnceCell::new(),
freevars_tuple: OnceCell::new(),
canonical_code,
name_owner: None,
code_bytes: OnceCell::new(),
localsplus_names,
source_path: AtomicPtr::new(sp),
@@ -739,6 +741,18 @@ impl PyCode {
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 {
// SAFETY: always points to a valid &'static PyStrInterned (interned strings are never deallocated)
unsafe { &*self.source_path.load(Ordering::Relaxed) }
@@ -912,7 +926,7 @@ impl Comparable for PyCode {
let other = class_or_notimplemented!(Self, other);
let a = &zelf.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.posonlyarg_count == b.posonlyarg_count
&& a.kwonlyarg_count == b.kwonlyarg_count
@@ -951,7 +965,7 @@ impl Hashable for PyCode {
let code = &zelf.code;
// Hash a tuple of key attributes, matching CPython's code_hash
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.posonlyarg_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);
result.retain_name_subclass(args.name, &vm.ctx);
result.linetable_bytes = args.linetable;
result.exceptiontable_bytes = args.exceptiontable;
Ok(result)
@@ -1160,6 +1175,13 @@ impl Py<PyCode> {
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]
fn co_consts(&self) -> PyTupleRef {
self.code
@@ -1606,7 +1628,7 @@ impl Py<PyCode> {
let obj_name = match co_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 {
@@ -1735,6 +1757,7 @@ impl Py<PyCode> {
};
let mut result = PyCode::new(new_code, &vm.ctx);
result.retain_name_subclass(obj_name, &vm.ctx);
result.retain_normalized_names(names, None);
result.linetable_bytes = linetable;
result.exceptiontable_bytes = exceptiontable;
+26 -6
View File
@@ -203,8 +203,14 @@ fn spec_format_string(
spec: &CFormatSpec,
obj: PyObjectRef,
argument: FormatArgument<'_>,
format_start: usize,
) -> PyResult<Wtf8Buf> {
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!(
"unsupported format character '{}' ({:#x}) at index {index}",
ch.to_char_lossy(),
@@ -245,15 +251,27 @@ fn spec_format_string(
Ok(spec.format_number(i.as_bigint()).into())
} else {
Err(vm.new_type_error(format!(
"%{} format: an integer is required, not {}",
"{}: %{} requires an integer, not {}",
argument.description(vm)?,
spec.format_type.to_char(),
obj.class().name()
obj.class().slot_name()
)))
}
}
},
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())
}
CFormatType::Character(CCharacterType::Character) => {
@@ -504,7 +522,7 @@ pub(crate) fn cformat_string(
}
// dict
for (_, part) in format {
for (format_start, part) in format {
match part {
CFormatPart::Literal(literal) => result.push_wtf8(&literal),
CFormatPart::Spec(CFormatSpecKeyed { mapping_key, spec }) => {
@@ -516,6 +534,7 @@ pub(crate) fn cformat_string(
&spec,
value,
FormatArgument::Mapping(key.as_object()),
format_start,
)?;
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 is_tuple = values_obj.downcast_ref::<tuple::PyTuple>().is_some();
for (_, part) in format {
for (format_start, part) in format {
match part {
CFormatPart::Literal(literal) => result.push_wtf8(&literal),
CFormatPart::Spec(CFormatSpecKeyed { mut spec, .. }) => {
@@ -561,7 +580,8 @@ pub(crate) fn cformat_string(
} else {
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);
}
}
+42 -15
View File
@@ -336,22 +336,38 @@ fn update_registry(
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(),
fn check_module_matched(
pattern: &PyObject,
module: Option<&PyObject>,
filename: &Py<PyStr>,
vm: &VirtualMachine,
) -> PyResult<bool> {
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.
#[allow(clippy::too_many_arguments)]
fn filter_search(
category: &PyObject,
text: &PyObject,
lineno: usize,
module: &PyObject,
module: Option<&PyObject>,
filename: &Py<PyStr>,
filters: &PyListRef,
list_name: &str,
vm: &VirtualMachine,
@@ -380,7 +396,7 @@ fn filter_search(
}
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 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]
.try_int(vm)
.map_or(0, |v| v.as_u32_mask() as _);
@@ -397,7 +413,8 @@ fn get_filter(
category: &PyObject,
text: &PyObject,
lineno: usize,
module: &PyObject,
module: Option<&PyObject>,
filename: &Py<PyStr>,
vm: &VirtualMachine,
) -> PyResult {
if let Some(context_filters) = get_warnings_context_filters(vm)? {
@@ -406,6 +423,7 @@ fn get_filter(
text,
lineno,
module,
filename,
&context_filters,
"_warnings_context _filters",
vm,
@@ -416,7 +434,9 @@ fn get_filter(
}
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);
}
get_default_action(vm)
@@ -504,9 +524,9 @@ pub fn warn_explicit(
source: Option<PyObjectRef>,
vm: &VirtualMachine,
) -> PyResult<()> {
// Normalize module. None → silent return (late-shutdown safety).
let module = module.unwrap_or_else(|| normalize_module(&filename, vm));
if vm.is_none(&module) {
// Explicit None remains a late-shutdown silent return. An omitted module
// is matched against the filename by the canonical warnings helper.
if module.as_ref().is_some_and(|module| vm.is_none(module)) {
return Ok(());
}
@@ -543,7 +563,14 @@ pub fn warn_explicit(
}
// 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)
.map_err(|_| vm.new_type_error("action must be a string"))?;