Multi phase module init#6740
Conversation
📝 WalkthroughWalkthroughIntroduce InterpreterBuilder and migrate stdlib/native module wiring from single‑step make_module to multi‑phase PyModuleDef APIs (module_def + module_exec). VM bootstrap now creates → inserts into sys.modules → exec (with rollback on failure). Callers updated to builder-based initialization and stdlib_module_defs(ctx). Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant Builder as InterpreterBuilder (ctx)
participant DefList as stdlib_module_defs(&ctx)
participant BuilderActions as Builder.add_native_modules / build
participant Interpreter
participant VM as VirtualMachine
participant Sys as sys.modules
participant Def as PyModuleDef
User->>Builder: Interpreter::builder(settings)
Builder->>DefList: stdlib_module_defs(&builder.ctx)
DefList-->>Builder: Vec<&PyModuleDef>
User->>BuilderActions: add_native_modules(defs) / build()
BuilderActions->>Interpreter: initialize_main_vm(...)
Interpreter->>VM: construct VM with module_defs map
loop for each builtin module_def
VM->>Def: create_module(vm) %% Phase 1: create
Def-->>VM: PyModule instance
VM->>Sys: insert module into sys.modules (pre-exec)
VM->>Def: exec_module(vm, module) %% Phase 2: exec
Def-->>VM: module init completes or error
alt exec failed
VM->>Sys: remove partial module
end
end
Interpreter-->>User: Interpreter (with global_state)
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Sorry, something went wrong.
660f2fb to
054b125
Compare
January 16, 2026 06:32
|
Code has been automatically formatted The code in this PR has been formatted using:
git pull origin multi-phase-module-init |
Sorry, something went wrong.
338374a to
5b2598b
Compare
January 16, 2026 06:51
85d072d to
ea3a345
Compare
January 21, 2026 12:11
ea3a345 to
5eed597
Compare
January 21, 2026 13:39
5eed597 to
c6dc3d2
Compare
January 21, 2026 14:07
c6dc3d2 to
7a38e56
Compare
January 21, 2026 14:35
There was a problem hiding this comment.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
crates/vm/src/stdlib/os.rs (1)
1900-1923: Avoid panics inmodule_exec; propagate errors instead.
unwrap()during import can abort the interpreter. ReturningPyResult<()>and using?provides a proper Python exception instead. If you adopt this, update callers to handle the new result.🛠️ Proposed fix
-pub fn module_exec(vm: &VirtualMachine, module: &Py<PyModule>) { +pub fn module_exec(vm: &VirtualMachine, module: &Py<PyModule>) -> PyResult<()> { let support_funcs = _os::support_funcs(); let supports_fd = PySet::default().into_ref(&vm.ctx); let supports_dir_fd = PySet::default().into_ref(&vm.ctx); let supports_follow_symlinks = PySet::default().into_ref(&vm.ctx); for support in support_funcs { - let func_obj = module.get_attr(support.name, vm).unwrap(); + let func_obj = module.get_attr(support.name, vm)?; if support.fd.unwrap_or(false) { - supports_fd.clone().add(func_obj.clone(), vm).unwrap(); + supports_fd.clone().add(func_obj.clone(), vm)?; } if support.dir_fd.unwrap_or(false) { - supports_dir_fd.clone().add(func_obj.clone(), vm).unwrap(); + supports_dir_fd.clone().add(func_obj.clone(), vm)?; } if support.follow_symlinks.unwrap_or(false) { - supports_follow_symlinks.clone().add(func_obj, vm).unwrap(); + supports_follow_symlinks.clone().add(func_obj, vm)?; } } extend_module!(vm, module, { "supports_fd" => supports_fd, "supports_dir_fd" => supports_dir_fd, "supports_follow_symlinks" => supports_follow_symlinks, "error" => vm.ctx.exceptions.os_error.to_owned(), }); + Ok(()) }
🤖 Fix all issues with AI agents
In `@crates/vm/src/import.rs`:
- Around line 91-116: The module is inserted into sys_modules via
sys_modules.set_item(...) before def.exec_module(...) and if exec_module fails
the partially-initialized module remains in sys_modules; update the two-phase
init path (the block that uses vm.state.module_defs, def.create_module,
sys_modules.set_item, def.exec_module) to remove the module from sys_modules on
exec failure (call sys_modules.del_item(module_name, vm) or equivalent) before
returning the error so subsequent imports won't return the broken module,
ensuring the original error from def.exec_module is propagated by returning
Err(e).
In `@crates/vm/src/stdlib/errno.rs`:
- Around line 9-23: The module_exec function currently uses unwrap() when
calling errorcode.set_item(...) and module.set_attr(...), which will panic on
failure; change those unwrap() calls to propagate errors by using the ? operator
so the function returns a Python exception instead of aborting. Locate
module_exec (and the loop over super::ERROR_CODES) and replace the two .unwrap()
usages on set_item and set_attr with ? so the PyResult<()> return handles
failures; ensure signatures remain unchanged and error propagation compiles.
In `@crates/vm/src/stdlib/imp.rs`:
- Around line 122-149: When inserting the module into sys.modules before calling
the def.slots.exec slot (module variable, sys_modules.set_item and
def.slots.exec), ensure you remove that entry if exec returns an Err so a
partially-initialized module is not left cached; change the exec call to
match/if-let on its Result, and on Err call sys_modules.del_item(&*name, vm) (or
the equivalent removal API) before propagating the error so behavior matches
CPython’s cleanup on exec failure.
In `@crates/vm/src/stdlib/mod.rs`:
- Around line 96-104: The posix::module_def(ctx) is being registered twice for
WASI because both #[cfg(target_os = "wasi")] and the broader
#[cfg(all(any(not(target_arch = "wasm32"), target_os = "wasi"), not(any(unix,
windows))))] match; remove the redundant explicit #[cfg(target_os = "wasi")]
posix::module_def(ctx) entry so posix::module_def(ctx) is only included once
(leave the broader cfg that covers non-unix/windows and WASI targets or vice
versa based on project convention), ensuring only a single
posix::module_def(ctx) call remains in the builtin modules list.
In `@crates/vm/src/vm/interpreter.rs`:
- Around line 30-142: The code in initialize_main_vm currently calls Box::leak
for sysconfigdata_name on every VM creation, leaking memory each time; change
this to leak the platform-specific name only once per process by introducing a
static OnceLock/OnceCell (e.g., static SYSCONFIGDATA_LEAK: OnceLock<&'static
str>) and use get_or_init to perform Box::leak only the first time, then insert
the returned &'static str into all_module_defs; reference initialize_main_vm,
sysconfigdata_name, and the all_module_defs insertion where leaked_name is
created to locate the change.
🧹 Nitpick comments (5)
crates/wasm/src/js_module.rs (1)
623-623: LGTM - Re-export added for module_def pattern.The addition of
pub(crate) use _js::module_def;aligns with the crate-wide module initialization refactoring. Note that this re-export is placed at the end of the file, unlike most other modules where it appears at the top. While functionally equivalent, you may consider moving it to line 2 (after thePyJsValue, PyPromisere-export) for consistency with other stdlib modules.crates/vm/src/builtins/module.rs (1)
53-71: Remove redundant import.
PyPayloadis already imported at the file level (line 3), so theuse crate::PyPayload;on line 54 is unnecessary.Proposed fix
pub fn create_module(&'static self, vm: &VirtualMachine) -> PyResult<PyRef<PyModule>> { - use crate::PyPayload; - // Create module (use create slot if provided, else default creation) let module = if let Some(create) = self.slots.create {crates/stdlib/src/_sqlite3.rs (1)
847-867: Propagate setup_module_exceptions errors instead of panicking.Line 856 invokes
setup_module_exceptions, which still usesunwrap()internally. Sincemodule_execnow returnsPyResult<()>, consider returningPyResult<()>fromsetup_module_exceptionsand using?to avoid panics during module init.crates/vm/src/stdlib/sysconfigdata.rs (1)
14-25: Chain__module_execbefore custom init.Custom
module_execdoesn’t call the macro-generated__module_exec, so any#[pyattr]/#[pyfunction]exports added now or later won’t be registered. Consider calling it first, consistent with other stdlib modules.♻️ Suggested change
fn module_exec(vm: &VirtualMachine, module: &Py<PyModule>) -> PyResult<()> { + __module_exec(vm, module); // Set build_time_vars attribute let build_time_vars = build_time_vars(vm);crates/vm/src/stdlib/sys.rs (1)
168-173: Consider deduplicating builtin module names after augmentation.If any native module definition already uses
"sys"or"builtins", this could yield duplicates insys.builtin_module_names. A quickdedupafter sorting prevents that edge case.♻️ Proposed tweak
- module_names.sort(); + module_names.sort(); + module_names.dedup();
Sorry, something went wrong.
There was a problem hiding this comment.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
crates/vm/src/stdlib/sys.rs (1)
1678-1685: Revert visibility change forsysconfigdata_name.The function is only called from within
crates/vm/src/(viacrate::stdlib::sys::sysconfigdata_name()ininterpreter.rsandsysconfigdata.rs), sopub(crate)visibility is sufficient. No external crates use this function. Change it back topub(crate)to maintain proper encapsulation.
♻️ Duplicate comments (1)
crates/vm/src/vm/interpreter.rs (1)
67-72: Avoid per-interpreterBox::leakfor_sysconfigdataaliasing.This repeats a previously flagged issue: leaking on each interpreter creation grows memory unboundedly. Prefer a process-wide
OnceLockto leak once.🩹 Suggested fix (same as prior review)
if let Some(&sysconfigdata_def) = all_module_defs.get("_sysconfigdata") { + use std::sync::OnceLock; let sysconfigdata_name = crate::stdlib::sys::sysconfigdata_name(); // Leak the string to get a 'static lifetime for the key - let leaked_name: &'static str = Box::leak(sysconfigdata_name.into_boxed_str()); - all_module_defs.insert(leaked_name, sysconfigdata_def); + static SYSCONFIGDATA_NAME: OnceLock<&'static str> = OnceLock::new(); + let leaked_name = + *SYSCONFIGDATA_NAME.get_or_init(|| Box::leak(sysconfigdata_name.into_boxed_str())); + all_module_defs.insert(leaked_name, sysconfigdata_def); }
Sorry, something went wrong.
7a38e56 to
203756d
Compare
January 21, 2026 14:54
b25c907 to
22ea6ad
Compare
January 21, 2026 15:14
22ea6ad to
bed12ed
Compare
January 21, 2026 23:58
📦 Library DependenciesThe following Lib/ modules were modified. Here are their dependencies: Click to expand dependency informationLegend:
|
Sorry, something went wrong.
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
crates/vm/src/vm/mod.rs (1)
1339-1339: Update test to use builder pattern for consistency.
test_nested_frozen(line 1314) usesInterpreter::builder(), butfrozen_origname_matches(line 1339) still usesInterpreter::with_init(). Update the latter to match the builder pattern for consistency with the rest of the test suite.
🤖 Fix all issues with AI agents
In `@crates/stdlib/src/_sqlite3.rs`:
- Around line 847-867: The call to __module_exec(vm, module) in module_exec
currently discards its Result; change the invocation to propagate errors (e.g.,
use __module_exec(vm, module)? or otherwise handle its PyResult) so that any
error returned by __module_exec is forwarded from module_exec; ensure
module_exec still returns PyResult<()> and that subsequent initialization
(ERROR_CODES loop, setup_module_exceptions,
CONVERTERS/ADAPTERS/USER_FUNCTION_EXCEPTION/ENABLE_TRACEBACK setup, and
module.set_attr calls) only runs after successful __module_exec.
In `@crates/vm/src/stdlib/ctypes.rs`:
- Around line 1281-1312: The call to __module_exec(vm, module) in module_exec
currently discards its PyResult return value; change the call to propagate
errors by using the ? operator (i.e., call __module_exec(vm, module)?;) so that
module_exec (which returns PyResult<()>) will return any error from
__module_exec instead of silently ignoring it.
In `@crates/vm/src/stdlib/typing.rs`:
- Around line 191-218: The call to __module_exec(vm, module) inside module_exec
currently discards its PyResult, so initialization errors are ignored; change
the call to propagate errors (e.g., use the ? operator) so that module_exec
returns early with the error when __module_exec fails—update the invocation in
the module_exec function to propagate the result rather than ignoring it.
In `@crates/wasm/src/vm_class.rs`:
- Around line 29-35: In module_exec, the result of __module_exec(vm, module) is
ignored so errors don't propagate; change the call to use the ? operator (i.e.,
__module_exec(vm, module)?;) so any PyErr returned by __module_exec is
propagated, then proceed to extend_module! and return Ok(()) from module_exec;
this ensures module initialization failures are handled consistently.
♻️ Duplicate comments (2)
crates/vm/src/stdlib/imp.rs (1)
143-149: Clean upsys.moduleswhen exec slot fails.This was flagged in a previous review: if
def.slots.execreturns an error on line 148, the module inserted at line 144 remains insys.modules, leaving a partially initialized module cached. This can cause issues with subsequent imports.🔧 Suggested fix
// Add to sys.modules BEFORE exec (critical for circular import handling) sys_modules.set_item(&*name, module.clone().into(), vm)?; // Phase 2: Call exec slot (can safely import other modules now) if let Some(exec) = def.slots.exec { - exec(vm, &module)?; + if let Err(err) = exec(vm, &module) { + let _ = sys_modules.del_item(&*name, vm); + return Err(err); + } }crates/vm/src/stdlib/mod.rs (1)
96-102: Potential duplicateposixmodule registration on WASI targets.The cfg conditions may still overlap for WASI:
- Line 96-97:
#[cfg(any(unix, target_os = "wasi"))]matches WASI- Lines 98-101:
#[cfg(all(any(not(target_arch = "wasm32"), target_os = "wasi"), not(any(unix, windows))))]also matches WASI sincetarget_os = "wasi"satisfies the first part and WASI is neither unix nor windowsThis could cause
posix::module_def(ctx)to be included twice in the vector for WASI builds.#!/bin/bash # Verify the cfg conditions for WASI target # Check if both conditions can be true simultaneously for wasi echo "Checking posix module cfg conditions in mod.rs..." rg -n "posix::module_def" crates/vm/src/stdlib/mod.rs -B2 -A1 echo "" echo "Checking how other files handle WASI posix module:" rg -n "target_os.*wasi" crates/vm/src/stdlib/mod.rs
🧹 Nitpick comments (4)
crates/vm/src/vm/interpreter.rs (1)
438-474: Consider tracking the FIXME about duplicate frozen core_modules.
If you’d like, I can help draft a follow-up to avoid double-generation while keepingwithout_stdlib()andinit_stdlib()consistent.crates/derive-impl/src/pymodule.rs (1)
56-90: Consider guardingdef.slots.execassignment or documenting thatmodule_execmust not be cfg-gated.The detection at lines 86-90 sets
has_module_exec = truebased on the function name alone, without checkingfunc.attrsfor#[cfg]guards. While no cfg-gatedmodule_execfunctions currently exist in the codebase (all use conditional code inside the function body instead), the implementation remains fragile: if a user adds a top-level#[cfg]-gatedmodule_exec, the fallback suppression (line 149) combined with the unconditional reference at line 142 would cause silent build failures on excluded configurations.Prevent this by either checking cfg attributes during detection, conditionally wrapping the
def.slots.execassignment, or documenting thatmodule_execcannot be cfg-gated at the function level.crates/vm/src/stdlib/os.rs (1)
1900-1924: Consider error propagation inmodule_exec.The function uses multiple
unwrap()calls (lines 1906, 1908, 1911, 1914) that could panic during module initialization ifget_attroraddfails. While the current callers inposix.rsandnt.rsdon't handle errors from this function, propagating errors would be more consistent with the pattern inerrno.rswhich uses?for error handling.Given the callers currently ignore the result anyway (they just call
os::module_exec(vm, module);), this is a lower-priority refactor that could be addressed in a follow-up.♻️ Suggested change for error propagation
-pub fn module_exec(vm: &VirtualMachine, module: &Py<PyModule>) { +pub fn module_exec(vm: &VirtualMachine, module: &Py<PyModule>) -> PyResult<()> { let support_funcs = _os::support_funcs(); let supports_fd = PySet::default().into_ref(&vm.ctx); let supports_dir_fd = PySet::default().into_ref(&vm.ctx); let supports_follow_symlinks = PySet::default().into_ref(&vm.ctx); for support in support_funcs { - let func_obj = module.get_attr(support.name, vm).unwrap(); + let func_obj = module.get_attr(support.name, vm)?; if support.fd.unwrap_or(false) { - supports_fd.clone().add(func_obj.clone(), vm).unwrap(); + supports_fd.clone().add(func_obj.clone(), vm)?; } if support.dir_fd.unwrap_or(false) { - supports_dir_fd.clone().add(func_obj.clone(), vm).unwrap(); + supports_dir_fd.clone().add(func_obj.clone(), vm)?; } if support.follow_symlinks.unwrap_or(false) { - supports_follow_symlinks.clone().add(func_obj, vm).unwrap(); + supports_follow_symlinks.clone().add(func_obj, vm)?; } } // ... extend_module! ... + Ok(()) }This would require updating callers in
posix.rs,nt.rs, andposix_compat.rsto propagate the error.crates/vm/src/vm/mod.rs (1)
231-232: Minor grammar issue in error message.Consider: "
rustpython_pylibmay not be set" instead of "maybe not set" for grammatical correctness.Suggested fix
let guide_message = if cfg!(feature = "freeze-stdlib") { - "`rustpython_pylib` maybe not set while using `freeze-stdlib` feature. Try using `rustpython::InterpreterBuilder::init_stdlib` or manually call `builder.add_frozen_modules(rustpython_pylib::FROZEN_STDLIB)` in `rustpython_vm::Interpreter::builder()`." + "`rustpython_pylib` may not be set while using `freeze-stdlib` feature. Try using `rustpython::InterpreterBuilder::init_stdlib` or manually call `builder.add_frozen_modules(rustpython_pylib::FROZEN_STDLIB)` in `rustpython_vm::Interpreter::builder()`."
Sorry, something went wrong.
bed12ed to
04a9da3
Compare
January 22, 2026 00:31
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
crates/vm/src/stdlib/os.rs (1)
1900-1923: Replaceunwrap()calls inos::module_execwith?operator; change return type toPyResult<()>and update callers.The current
module_execfunction panics onget_attr()oradd()failures (OOM, missing attributes). Change the return type toPyResult<()>, replace the fourunwrap()calls with?operators, and update the three callers inposix.rs,nt.rs, andposix_compat.rsto use?when callingsuper::super::os::module_exec(vm, module).🔧 Proposed fix
-pub fn module_exec(vm: &VirtualMachine, module: &Py<PyModule>) { +pub fn module_exec(vm: &VirtualMachine, module: &Py<PyModule>) -> PyResult<()> { let support_funcs = _os::support_funcs(); let supports_fd = PySet::default().into_ref(&vm.ctx); let supports_dir_fd = PySet::default().into_ref(&vm.ctx); let supports_follow_symlinks = PySet::default().into_ref(&vm.ctx); for support in support_funcs { - let func_obj = module.get_attr(support.name, vm).unwrap(); + let func_obj = module.get_attr(support.name, vm)?; if support.fd.unwrap_or(false) { - supports_fd.clone().add(func_obj.clone(), vm).unwrap(); + supports_fd.clone().add(func_obj.clone(), vm)?; } if support.dir_fd.unwrap_or(false) { - supports_dir_fd.clone().add(func_obj.clone(), vm).unwrap(); + supports_dir_fd.clone().add(func_obj.clone(), vm)?; } if support.follow_symlinks.unwrap_or(false) { - supports_follow_symlinks.clone().add(func_obj, vm).unwrap(); + supports_follow_symlinks.clone().add(func_obj, vm)?; } } extend_module!(vm, module, { "supports_fd" => supports_fd, "supports_dir_fd" => supports_dir_fd, "supports_follow_symlinks" => supports_follow_symlinks, "error" => vm.ctx.exceptions.os_error.to_owned(), }); + Ok(()) }Update callers:
posix.rs:super::super::os::module_exec(vm, module)?;nt.rs:super::super::os::module_exec(vm, module)?;posix_compat.rs:super::super::os::module_exec(vm, module)?;This aligns with the pattern used in other stdlib modules (
sysconfigdata,errno,io,signal) that returnPyResult<()>and propagate errors rather than panicking during initialization.
🤖 Fix all issues with AI agents
In `@crates/derive-impl/src/pymodule.rs`:
- Around line 85-90: The derive currently marks context.has_module_exec = true
when it sees an Item::Fn named module_exec but doesn't handle cfg-gated
declarations, which leads to emitting def.slots.exec = Some(module_exec)
unconditionally; update the visitor in pymodule.rs to detect if the
function/item named module_exec has any #[cfg(...)] attributes (inspect
func.attrs for a Meta path "cfg") and, if so, emit a compile_error (or return a
syn::Error) rejecting cfg‑gated module_exec with a clear message that the cfg
must be moved to the containing module or a non-gated fallback provided, instead
of setting context.has_module_exec = true and generating def.slots.exec =
Some(module_exec).
In `@crates/stdlib/src/pyexpat.rs`:
- Around line 53-65: The submodules "model" and "errors" created in module_exec
(variables model and errors) are attached to the pyexpat module but not
registered in sys.modules with their qualified names; update module_exec to
insert entries for "pyexpat.model" and "pyexpat.errors" into the VM's
sys.modules mapping after creating the submodules and before returning (use
vm.sys_module or the VM API to access sys.modules), ensuring the Py<PyModule>
values for model and errors are stored under those qualified keys so imports and
introspection that expect sys.modules["pyexpat.model"] /
sys.modules["pyexpat.errors"] will work as in CPython.
In `@crates/vm/src/stdlib/ast/python.rs`:
- Around line 100-107: The call to __module_exec(...) currently ignores its
PyResult, letting initialization errors be dropped; change module_exec to
propagate the error from __module_exec (e.g. use the ? operator or return on
Err) before calling super::super::pyast::extend_module_nodes so that failures in
__module_exec are returned as Err(PyErr) instead of always returning Ok(()).
Ensure you reference the existing functions __module_exec and module_exec and
preserve the subsequent call to extend_module_nodes only after successful
__module_exec.
In `@crates/vm/src/stdlib/sys.rs`:
- Around line 168-174: The returned tuple from builtin_module_names may contain
duplicate "sys" or "builtins" entries; in the builtin_module_names function,
after collecting and pushing module names (module_names) and sorting, remove
duplicates (e.g., dedupe the Vec<String> in-place) before converting to a
PyTupleRef so the exported tuple contains unique module names only.
♻️ Duplicate comments (5)
crates/wasm/src/vm_class.rs (1)
24-35: Propagate__module_execerrors (already noted earlier).
Line 30 ignores thePyResultfrom__module_exec, so init errors won’t propagate.crates/vm/src/stdlib/typing.rs (1)
191-218: Propagate __module_exec errors.Same issue:
__module_exec(vm, module);discards errors; use?to avoid silent init failures.crates/vm/src/stdlib/ctypes.rs (1)
1281-1311: Propagate __module_exec errors.Same issue: ignoring the Result hides init failures.
crates/vm/src/stdlib/imp.rs (1)
122-149: Removesys.modulesentry if exec fails.If the exec slot errors, a partially initialized module remains cached. Clean it up before propagating the error to match CPython’s behavior.
🔧 Proposed fix
- if let Some(exec) = def.slots.exec { - exec(vm, &module)?; - } + if let Some(exec) = def.slots.exec { + if let Err(err) = exec(vm, &module) { + let _ = sys_modules.del_item(&*name, vm); + return Err(err); + } + }crates/vm/src/stdlib/mod.rs (1)
96-102: Avoid duplicateposixmodule registration on WASI.Both cfgs can match for
target_os = "wasi", causingposix::module_def(ctx)to be included twice. Consider narrowing the first entry to unix-only (or remove one entry).🛠 Suggested fix
- #[cfg(any(unix, target_os = "wasi"))] + #[cfg(unix)] posix::module_def(ctx),
Sorry, something went wrong.
04a9da3 to
1087d6c
Compare
January 22, 2026 01:15
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Fix all issues with AI agents
In `@crates/stdlib/src/openssl.rs`:
- Around line 100-108: module_exec currently ignores the PyResult returned by
__module_exec, masking initialization errors; change module_exec to propagate
that result instead of always returning Ok(()). After the existing
LazyLock::force call, call __module_exec(vm, module) and return its PyResult
(either via the ? operator or by returning the result directly) so any error
from __module_exec is forwarded to the caller.
In `@crates/vm/src/builtins/module.rs`:
- Around line 44-81: The create_module implementation must guard against create
slots returning modules with no def and pass a consistent spec object: when
self.slots.create is Some, build and pass a module spec object (not just a
string) compatible with imp.rs::create_builtin (i.e., an object with a name
attribute), then call the create slot; after creation, check the returned
module's def (module.def or module.def.is_some()) and only call
PyModule::__init_dict_from_def and module.__init_methods if the def is present;
otherwise skip those calls to avoid panics for modules created via
PyModule::new().
In `@crates/vm/src/stdlib/errno.rs`:
- Around line 9-22: The module_exec function currently calls __module_exec(vm,
module) and discards its PyResult, which can hide initialization errors; change
the call to propagate errors by using the ? operator (i.e., __module_exec(vm,
module)?;) so module_exec returns early with the underlying PyErr when
__module_exec fails, keeping the function's PyResult return type semantics
intact.
In `@crates/vm/src/stdlib/time.rs`:
- Around line 577-588: The module initialization currently calls
__module_exec(vm, module) but discards its PyResult, masking failures; update
module_exec to propagate errors by returning the result of __module_exec(vm,
module) (i.e., replace the ignored call + Ok(()) with returning __module_exec's
PyResult), preserving the existing unsafe c_tzset call and signature of
module_exec (working with VirtualMachine and Py<crate::builtins::PyModule>).
♻️ Duplicate comments (7)
crates/stdlib/src/pyexpat.rs (1)
42-66: Register pyexpat submodules in sys.modules for qualified importsThe module still attaches
model/errorsas attributes without registeringpyexpat.model/pyexpat.errorsinsys.modules, which can break qualified imports and introspection.crates/vm/src/stdlib/imp.rs (1)
118-149: Remove partially initialized modules on exec failure.
Ifexecerrors, the module stays insys.modulesand may be reused in a bad state.🛠️ Suggested fix
- if let Some(exec) = def.slots.exec { - exec(vm, &module)?; - } + if let Some(exec) = def.slots.exec { + if let Err(err) = exec(vm, &module) { + let _ = sys_modules.del_item(&*name, vm); + return Err(err); + } + }crates/vm/src/stdlib/typing.rs (1)
191-218: Propagate__module_execfailures.
The result is dropped; initialization errors can be silently ignored.🛠️ Suggested fix
- __module_exec(vm, module); + __module_exec(vm, module)?;crates/vm/src/stdlib/ctypes.rs (1)
1281-1312: Propagate__module_execfailures.
The result is dropped; initialization errors can be silently ignored.🛠️ Suggested fix
- __module_exec(vm, module); + __module_exec(vm, module)?;crates/vm/src/stdlib/sys.rs (1)
168-174: Duplicate: dedupebuiltin_module_namesafter sorting.
module_defscan already include"sys"/"builtins", so the tuple may contain duplicates.crates/derive-impl/src/pymodule.rs (1)
85-90: Duplicate: reject cfg‑gatedmodule_execto avoid missing symbol.crates/vm/src/stdlib/mod.rs (1)
96-102: Duplicateposixmodule registration on WASI targets.On WASI targets, both conditions match:
#[cfg(any(unix, target_os = "wasi"))](line 96) matches becausetarget_os = "wasi"#[cfg(all(any(not(target_arch = "wasm32"), target_os = "wasi"), not(any(unix, windows))))](lines 98-101) also matches because WASI iswasm32but hastarget_os = "wasi", and WASI is neitherunixnorwindowsThis results in
posix::module_def(ctx)being included twice in the vector for WASI builds, which could cause issues during module registration.🔧 Suggested fix
#[cfg(any(unix, target_os = "wasi"))] posix::module_def(ctx), #[cfg(all( any(not(target_arch = "wasm32"), target_os = "wasi"), - not(any(unix, windows)) + not(any(unix, windows, target_os = "wasi")) ))] posix::module_def(ctx),Alternatively, remove the WASI-specific entry since it's already covered:
- #[cfg(any(unix, target_os = "wasi"))] - posix::module_def(ctx), + #[cfg(unix)] + posix::module_def(ctx), + #[cfg(target_os = "wasi")] + posix::module_def(ctx), #[cfg(all( any(not(target_arch = "wasm32"), target_os = "wasi"), not(any(unix, windows)) ))] posix::module_def(ctx),
🧹 Nitpick comments (2)
src/interpreter.rs (2)
30-37: Potential panic if state has multiple references.The
.unwrap()onPyRc::get_mutwill panic if there are other references tovm.state. While this is likely safe during initialization (wheninit_hookruns), consider usingexpect()with a descriptive message to aid debugging if assumptions are violated.- let state = PyRc::get_mut(&mut vm.state).unwrap(); + let state = PyRc::get_mut(&mut vm.state) + .expect("VM state should be uniquely owned during init_hook");
58-76: Potential panic on non-UTF-8 paths.The
.unwrap()oninto_string()(line 64) will panic ifBUILDTIME_RUSTPYTHONPATHcontains non-UTF-8 characters. While unlikely in controlled build environments, consider usingto_string_lossy()or handling the error gracefully:- .map(|path| path.into_os_string().into_string().unwrap()) + .filter_map(|path| path.into_os_string().into_string().ok())Alternatively, if non-UTF-8 paths should cause a build/runtime failure, document this expectation.
Sorry, something went wrong.
1087d6c to
2037645
Compare
January 22, 2026 02:21
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@crates/vm/src/stdlib/io.rs`:
- Around line 4732-4744: The fileio module is missing a pub(crate) module_exec
function required by the call super::fileio::module_exec(vm, module)?; — add a
minimal wrapper with the signature pub(crate) fn module_exec(vm:
&VirtualMachine, module: &Py<PyModule>) -> PyResult<()> that calls the
autogenerated __module_exec(vm, module) and returns Ok(()), so the call site
compiles on non-WASM platforms; reference the __module_exec symbol inside the
new fileio::module_exec.
♻️ Duplicate comments (7)
crates/vm/src/stdlib/ctypes.rs (1)
1281-1288: Propagate errors from__module_exec.Line 1287 discards the
PyResultreturn; errors will be silently ignored.🔧 Suggested fix
- __module_exec(vm, module); + __module_exec(vm, module)?;crates/stdlib/src/pyexpat.rs (1)
53-63: Registerpyexpat.model/pyexpat.errorsinsys.modulesfor qualified imports.crates/vm/src/stdlib/imp.rs (1)
143-149: Rollback sys.modules if exec fails.Line 143 inserts the module before exec; if exec errors, the partially initialized module remains cached. Clean it up on failure to avoid stale imports.
🛠️ Proposed fix
- if let Some(exec) = def.slots.exec { - exec(vm, &module)?; - } + if let Some(exec) = def.slots.exec { + if let Err(err) = exec(vm, &module) { + let _ = sys_modules.del_item(&*name, vm); + return Err(err); + } + }crates/derive-impl/src/pymodule.rs (1)
85-90: Guard against cfg‑gatedmodule_execto avoid build breaks.
Line 85–90 marks anymodule_execas present, butdef.slots.exec = Some(module_exec)is always emitted. If a user#[cfg]‑gatesmodule_exec, builds with the cfg disabled will fail due to an unresolved symbol. This was previously flagged; consider rejecting cfg‑gatedmodule_execor requiring a non‑gated fallback.🛠️ Proposed fix
- if let Item::Fn(func) = item - && func.sig.ident == "module_exec" - { - context.has_module_exec = true; - } + if let Item::Fn(func) = item + && func.sig.ident == "module_exec" + { + let has_cfg = func.attrs.iter().any(|a| a.path().is_ident("cfg")); + if has_cfg { + context.errors.push(syn::Error::new_spanned( + &func.sig.ident, + "`module_exec` must not be cfg-gated; move the cfg to the module or provide a non-gated fallback", + )); + } else { + context.has_module_exec = true; + } + }#!/bin/bash # Find cfg-gated module_exec definitions to validate impact. rg -n --type=rust '#\[cfg[^\]]*\]\s*\n\s*fn\s+module_exec' -C2crates/vm/src/stdlib/mod.rs (1)
96-102: Avoid duplicateposix::module_defregistration on WASI.
Both cfg blocks match on WASI, soposix::module_def(ctx)is added twice. Keep a single entry to prevent double registration.🛠 Suggested fix
- #[cfg(any(unix, target_os = "wasi"))] - posix::module_def(ctx), + #[cfg(unix)] + posix::module_def(ctx),crates/wasm/src/vm_class.rs (1)
29-34: Propagate__module_execerrors.
The return value is ignored, so init failures won’t surface.🔧 Suggested fix
- __module_exec(vm, module); + __module_exec(vm, module)?;crates/vm/src/stdlib/sys.rs (1)
168-174: Deduplicatesys.builtin_module_namesentries.
module_defsmay already containsys/builtins, so pushing them unconditionally can introduce duplicates in the exported tuple. Adddedup()after sorting to ensure uniqueness.🩹 Proposed fix
module_names.sort(); + module_names.dedup();
🧹 Nitpick comments (2)
crates/vm/src/stdlib/sysconfigdata.rs (1)
14-23: Optional: skip redundant sys.modules overwrite.At Line 21–23, consider checking if
sys.modulesalready contains the platform-specific key before setting it to avoid unnecessary mutation during reloads.crates/vm/src/vm/interpreter.rs (1)
130-146: Consider returningglobal_statefrom the VM rather than cloning.The comment on line 131 mentions "Don't clone here" but line 145 does clone
vm.state. Sincevm.stateis already aPyRc<PyGlobalState>, the clone is cheap (just reference count bump). However, the function could simply returnvm.state.clone()directly without the intermediate variable for clarity.♻️ Optional simplification
vm.initialize(); - // Clone global_state for Interpreter after all initialization is done - let global_state = vm.state.clone(); - (vm, global_state) + (vm, vm.state.clone()) }
Sorry, something went wrong.
47df6dd
into
RustPython:main
Jan 22, 2026
Summary by CodeRabbit
New Features
Refactor
Behavior
✏️ Tip: You can customize this high-level summary in your review settings.