GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — [BREAKING CHANGE] Writing magic method names as full name by youknowone · Pull Request #5842 · RustPython/RustPython

[BREAKING CHANGE] Writing magic method names as full name #5842

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

Sign up for GitHub

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
youknowone merged 4 commits into RustPython:main from youknowone:magic-method-names
Jun 27, 2025

Uh oh!

There was an error while loading. Please reload this page.

Merged

[BREAKING CHANGE] Writing magic method names as full name #5842

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
4 commits
Select commit Hold shift + click to select a range
4378a61
Fix 1.80 build
youknowone Jun 27, 2025
ef3cf70
Remove magic from macro
youknowone Jun 26, 2025
993ea89
Rename members
youknowone Jun 26, 2025
3673372
Fix cspell warnings
youknowone Jun 27, 2025
File filter
Filter by extension


Clear filters
Conversations
Failed to load comments.
Loading

Uh oh!

There was an error while loading. Please reload this page.

Jump to
Jump to file
Failed to load files.
Loading

Uh oh!

There was an error while loading. Please reload this page.

Diff view
Diff view

There are no files selected for viewing

2 changes: 2 additions & 0 deletions 2 .cspell.dict/cpython.txt
Show comments
View file
Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ SA_ONSTACK
stackdepth
stringlib
structseq
subparams
tok_oldval
tvars
unaryop
unparse
unparser
Expand Down
6 changes: 6 additions & 0 deletions 6 .cspell.dict/python-more.txt
Show comments
View file
Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ getrandom
getrecursionlimit
getrefcount
getsizeof
getswitchinterval
getweakrefcount
getweakrefs
getwindowsversion
Expand Down Expand Up @@ -167,13 +168,17 @@ pycs
pyexpat
PYTHONBREAKPOINT
PYTHONDEBUG
PYTHONDONTWRITEBYTECODE
PYTHONHASHSEED
PYTHONHOME
PYTHONINSPECT
PYTHONINTMAXSTRDIGITS
PYTHONNOUSERSITE
PYTHONOPTIMIZE
PYTHONPATH
PYTHONPATH
PYTHONSAFEPATH
PYTHONUNBUFFERED
PYTHONVERBOSE
PYTHONWARNDEFAULTENCODING
PYTHONWARNINGS
Expand Down Expand Up @@ -206,6 +211,7 @@ seennl
setattro
setcomp
setrecursionlimit
setswitchinterval
showwarnmsg
signum
slotnames
Expand Down
4 changes: 4 additions & 0 deletions 4 .cspell.dict/rust-more.txt
Show comments
View file
Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ arrayvec
bidi
biguint
bindgen
bitand
bitflags
bitor
bitxor
bstr
byteorder
byteset
Expand All @@ -15,6 +17,7 @@ cranelift
cstring
datelike
deserializer
deserializers
fdiv
flamescope
flate2
Expand All @@ -31,6 +34,7 @@ keccak
lalrpop
lexopt
libc
libcall
libloading
libz
longlong
Expand Down
3 changes: 3 additions & 0 deletions 3 .cspell.json
Show comments
View file
Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@
"GetSet",
"groupref",
"internable",
"jitted",
"jitting",
"lossily",
"makeunicodedata",
"miri",
Expand All @@ -85,6 +87,7 @@
"pygetset",
"pyimpl",
"pylib",
"pymath",
"pymember",
"PyMethod",
"PyModule",
Expand Down
3 changes: 2 additions & 1 deletion 3 common/src/str.rs
Show comments
View file
Open in desktop
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// cspell:ignore uncomputed
use crate::atomic::{PyAtomic, Radium};
use crate::format::CharLen;
use crate::wtf8::{CodePoint, Wtf8, Wtf8Buf};
Expand Down Expand Up @@ -424,7 +425,7 @@ pub fn zfill(bytes: &[u8], width: usize) -> Vec<u8> {
}
}

/// Convert a string to ascii compatible, escaping unicodes into escape
/// Convert a string to ascii compatible, escaping unicode-s into escape
/// sequences.
pub fn to_ascii(value: &str) -> AsciiString {
let mut ascii = Vec::new();
Expand Down
26 changes: 13 additions & 13 deletions 26 compiler/codegen/src/compile.rs
Show comments
View file
Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -2361,7 +2361,7 @@ impl Compiler<'_> {
// self.jump_to_fail_pop(pc, JumpOp::PopJumpIfFalse)?;
// }

// // Check that the number of subpatterns is not absurd.
// // Check that the number of sub-patterns is not absurd.
// if size.saturating_sub(1) > (i32::MAX as usize) {
// panic!("too many sub-patterns in mapping pattern");
// // return self.compiler_error("too many sub-patterns in mapping pattern");
Expand Down Expand Up @@ -2469,41 +2469,41 @@ impl Compiler<'_> {
emit!(self, Instruction::CopyItem { index: 1_u32 });
self.compile_pattern(alt, pc)?;

let nstores = pc.stores.len();
let n_stores = pc.stores.len();
if i == 0 {
// Save the captured names from the first alternative.
control = Some(pc.stores.clone());
} else {
let control_vec = control.as_ref().unwrap();
if nstores != control_vec.len() {
if n_stores != control_vec.len() {
return Err(self.error(CodegenErrorType::ConflictingNameBindPattern));
} else if nstores > 0 {
} else if n_stores > 0 {
// Check that the names occur in the same order.
for icontrol in (0..nstores).rev() {
let name = &control_vec[icontrol];
for i_control in (0..n_stores).rev() {
let name = &control_vec[i_control];
// Find the index of `name` in the current stores.
let istores =
let i_stores =
pc.stores.iter().position(|n| n == name).ok_or_else(|| {
self.error(CodegenErrorType::ConflictingNameBindPattern)
})?;
if icontrol != istores {
if i_control != i_stores {
// The orders differ; we must reorder.
assert!(istores < icontrol, "expected istores < icontrol");
let rotations = istores + 1;
assert!(i_stores < i_control, "expected i_stores < i_control");
let rotations = i_stores + 1;
// Rotate pc.stores: take a slice of the first `rotations` items...
let rotated = pc.stores[0..rotations].to_vec();
// Remove those elements.
for _ in 0..rotations {
pc.stores.remove(0);
}
// Insert the rotated slice at the appropriate index.
let insert_pos = icontrol - istores;
let insert_pos = i_control - i_stores;
for (j, elem) in rotated.into_iter().enumerate() {
pc.stores.insert(insert_pos + j, elem);
}
// Also perform the same rotation on the evaluation stack.
for _ in 0..(istores + 1) {
self.pattern_helper_rotate(icontrol + 1)?;
for _ in 0..(i_stores + 1) {
self.pattern_helper_rotate(i_control + 1)?;
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion 2 compiler/src/lib.rs
Show comments
View file
Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ pub fn compile(
source_path: &str,
opts: CompileOpts,
) -> Result<CodeObject, CompileError> {
// TODO: do this less hackily; ruff's parser should translate a CRLF line
// TODO: do this less hacky; ruff's parser should translate a CRLF line
// break in a multiline string into just an LF in the parsed value
#[cfg(windows)]
let source = &source.replace("\r\n", "\n");
Expand Down
65 changes: 43 additions & 22 deletions 65 derive-impl/src/pyclass.rs
Show comments
View file
Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -752,6 +752,13 @@ where
let raw = item_meta.raw()?;
let sig_doc = text_signature(func.sig(), &py_name);

// Add #[allow(non_snake_case)] for setter methods like set___name__
let method_name = ident.to_string();
if method_name.starts_with("set_") && method_name.contains("__") {
let allow_attr: Attribute = parse_quote!(#[allow(non_snake_case)]);
args.attrs.push(allow_attr);
}

let doc = args.attrs.doc().map(|doc| format_doc(&sig_doc, &doc));
args.context.method_items.add_item(MethodNurseryItem {
py_name,
Expand Down Expand Up @@ -780,6 +787,13 @@ where
let item_meta = GetSetItemMeta::from_attr(ident.clone(), &item_attr)?;

let (py_name, kind) = item_meta.getset_name()?;

// Add #[allow(non_snake_case)] for setter methods
if matches!(kind, GetSetItemKind::Set) {
let allow_attr: Attribute = parse_quote!(#[allow(non_snake_case)]);
args.attrs.push(allow_attr);
}

args.context
.getset_items
.add_item(py_name, args.cfgs.to_vec(), kind, ident.clone())?;
Expand Down Expand Up @@ -934,6 +948,12 @@ where
_ => MemberKind::ObjectEx,
};

// Add #[allow(non_snake_case)] for setter methods
if matches!(member_item_kind, MemberItemKind::Set) {
let allow_attr: Attribute = parse_quote!(#[allow(non_snake_case)]);
args.attrs.push(allow_attr);
}

args.context.member_items.add_item(
py_name,
member_item_kind,
Expand Down Expand Up @@ -1208,7 +1228,7 @@ impl ToTokens for MemberNursery {
struct MethodItemMeta(ItemMetaInner);

impl ItemMeta for MethodItemMeta {
const ALLOWED_NAMES: &'static [&'static str] = &["name", "magic", "raw"];
const ALLOWED_NAMES: &'static [&'static str] = &["name", "raw"];

fn from_inner(inner: ItemMetaInner) -> Self {
Self(inner)
Expand All @@ -1225,27 +1245,18 @@ impl MethodItemMeta {
fn method_name(&self) -> Result<String> {
let inner = self.inner();
let name = inner._optional_str("name")?;
let magic = inner._bool("magic")?;
if magic && name.is_some() {
bail_span!(
&inner.meta_ident,
"A #[{}] method cannot be magic and have a specified name, choose one.",
inner.meta_name()
);
}
Ok(if let Some(name) = name {
name
} else {
let name = inner.item_name();
if magic { format!("__{name}__") } else { name }
inner.item_name()
})
}
}

struct GetSetItemMeta(ItemMetaInner);

impl ItemMeta for GetSetItemMeta {
const ALLOWED_NAMES: &'static [&'static str] = &["name", "magic", "setter", "deleter"];
const ALLOWED_NAMES: &'static [&'static str] = &["name", "setter", "deleter"];

fn from_inner(inner: ItemMetaInner) -> Self {
Self(inner)
Expand All @@ -1258,7 +1269,6 @@ impl ItemMeta for GetSetItemMeta {
impl GetSetItemMeta {
fn getset_name(&self) -> Result<(String, GetSetItemKind)> {
let inner = self.inner();
let magic = inner._bool("magic")?;
let kind = match (inner._bool("setter")?, inner._bool("deleter")?) {
(false, false) => GetSetItemKind::Get,
(true, false) => GetSetItemKind::Set,
Expand Down Expand Up @@ -1299,12 +1309,11 @@ impl GetSetItemMeta {
))
}
};
let name = match kind {
match kind {
GetSetItemKind::Get => sig_name,
GetSetItemKind::Set => extract_prefix_name("set_", "setter")?,
GetSetItemKind::Delete => extract_prefix_name("del_", "deleter")?,
};
if magic { format!("__{name}__") } else { name }
}
};
Ok((py_name, kind))
}
Expand Down Expand Up @@ -1353,7 +1362,7 @@ impl ItemMeta for SlotItemMeta {
impl SlotItemMeta {
fn slot_name(&self) -> Result<Ident> {
let inner = self.inner();
let slot_name = if let Some((_, meta)) = inner.meta_map.get("name") {
let method_name = if let Some((_, meta)) = inner.meta_map.get("name") {
match meta {
Meta::Path(path) => path.get_ident().cloned(),
_ => None,
Expand All @@ -1367,19 +1376,32 @@ impl SlotItemMeta {
};
Some(name)
};
slot_name.ok_or_else(|| {
let method_name = method_name.ok_or_else(|| {
err_span!(
inner.meta_ident,
"#[pyslot] must be of the form #[pyslot] or #[pyslot(slot_name)]",
)
})
})?;

// Strip double underscores from slot names like __init__ -> init
let method_name_str = method_name.to_string();
let slot_name = if method_name_str.starts_with("__")
&& method_name_str.ends_with("__")
&& method_name_str.len() > 4
{
&method_name_str[2..method_name_str.len() - 2]
} else {
&method_name_str
};

Ok(proc_macro2::Ident::new(slot_name, slot_name.span()))
}
}

struct MemberItemMeta(ItemMetaInner);

impl ItemMeta for MemberItemMeta {
const ALLOWED_NAMES: &'static [&'static str] = &["magic", "type", "setter"];
const ALLOWED_NAMES: &'static [&'static str] = &["type", "setter"];

fn from_inner(inner: ItemMetaInner) -> Self {
Self(inner)
Expand Down Expand Up @@ -1416,7 +1438,6 @@ impl MemberItemMeta {
))
}
};
let magic = inner._bool("magic")?;
let kind = if inner._bool("setter")? {
MemberItemKind::Set
} else {
Expand All @@ -1426,7 +1447,7 @@ impl MemberItemMeta {
MemberItemKind::Get => sig_name,
MemberItemKind::Set => extract_prefix_name("set_", "setter")?,
};
Ok((if magic { format!("__{name}__") } else { name }, kind))
Ok((name, kind))
}

fn member_kind(&self) -> Result<Option<String>> {
Expand Down
11 changes: 0 additions & 11 deletions 11 derive/src/lib.rs
Show comments
View file
Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -64,17 +64,6 @@ pub fn derive_from_args(input: TokenStream) -> TokenStream {
/// but so does any object that implements `PyValue`.
/// Consider using `OptionalArg` for optional arguments.
/// #### Arguments
/// - `magic`: marks the method as a magic method: the method name is surrounded with double underscores.
/// ```rust, ignore
/// #[pyclass]
/// impl MyStruct {
/// // This will be called as the `__add__` method in Python.
/// #[pymethod(magic)]
/// fn add(&self, other: &Self) -> PyResult<i32> {
/// ...
/// }
/// }
/// ```
/// - `name`: the name of the method in Python,
/// by default it is the same as the Rust method, or surrounded by double underscores if magic is present.
/// This overrides `magic` and the default name and cannot be used with `magic` to prevent ambiguity.
Expand Down
3 changes: 3 additions & 0 deletions 3 example_projects/barebone/Cargo.toml
Show comments
View file
Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@ edition = "2021"
rustpython-vm = { path = "../../vm", default-features = false }

[workspace]

[patch.crates-io]
radium = { version = "1.1.0", git = "https://github.com/youknowone/ferrilab", branch = "fix-nightly" }
3 changes: 3 additions & 0 deletions 3 example_projects/frozen_stdlib/Cargo.toml
Show comments
View file
Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,6 @@ rustpython-vm = { path = "../../vm", default-features = false, features = ["free
rustpython-pylib = { path = "../../pylib", default-features = false, features = ["freeze-stdlib"] }

[workspace]

[patch.crates-io]
radium = { version = "1.1.0", git = "https://github.com/youknowone/ferrilab", branch = "fix-nightly" }
1 change: 1 addition & 0 deletions 1 example_projects/frozen_stdlib/src/main.rs
Show comments
View file
Open in desktop
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// cspell:ignore aheui
/// Setting up a project with a frozen stdlib can be done *either* by using `rustpython::InterpreterConfig` or `rustpython_vm::Interpreter::with_init`.
/// See each function for example.
///
Expand Down
2 changes: 1 addition & 1 deletion 2 examples/parse_folder.rs
Show comments
View file
Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ fn parse_folder(path: &Path) -> std::io::Result<Vec<ParsedFile>> {
let parsed_file = parse_python_file(&path);
match &parsed_file.result {
Ok(_) => {}
Err(y) => error!("Erreur in file {path:?} {y:?}"),
Err(y) => error!("Error in file {path:?} {y:?}"),
}

res.push(parsed_file);
Expand Down
4 changes: 2 additions & 2 deletions 4 jit/tests/common.rs
Show comments
View file
Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,9 @@ impl StackMachine {
}

pub fn run(&mut self, code: CodeObject) {
let mut oparg_state = OpArgState::default();
let mut op_arg_state = OpArgState::default();
let _ = code.instructions.iter().try_for_each(|&word| {
let (instruction, arg) = oparg_state.get(word);
let (instruction, arg) = op_arg_state.get(word);
self.process_instruction(instruction, arg, &code.constants, &code.names)
});
}
Expand Down
Loading
Oops, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

Add this suggestion to a batch that can be applied as a single commit. This suggestion is invalid because no changes were made to the code. Suggestions cannot be applied while the pull request is closed. Suggestions cannot be applied while viewing a subset of changes. Only one suggestion per line can be applied in a batch. Add this suggestion to a batch that can be applied as a single commit. Applying suggestions on deleted lines is not supported. You must change the existing code in this line in order to create a valid suggestion. Outdated suggestions cannot be applied. This suggestion has been applied or marked resolved. Suggestions cannot be applied from pending reviews. Suggestions cannot be applied on multi-line comments. Suggestions cannot be applied while the pull request is queued to merge. Suggestion cannot be applied right now. Please check back later.
You are visitor 01402520 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.