Skip to content

Commit b4123a2

Browse files
committed
temp
1 parent ede24aa commit b4123a2

File tree

9 files changed

+375
-26
lines changed

9 files changed

+375
-26
lines changed

Lib/test/test_importlib/frozen/test_loader.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from test.support import captured_stdout, import_helper, STDLIB_DIR
66
import contextlib
77
import os.path
8+
import sys
89
import types
910
import unittest
1011
import warnings

Lib/test/test_importlib/test_locks.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,10 @@ def test_all_locks(self):
153153
Source_LifetimeTests
154154
) = test_util.test_both(LifetimeTests, init=init)
155155

156+
# TODO: RUSTPYTHON; dead weakref module locks not cleaned up in frozen bootstrap
157+
Frozen_LifetimeTests.test_all_locks = unittest.skip("TODO: RUSTPYTHON")(
158+
Frozen_LifetimeTests.test_all_locks)
159+
156160

157161
def setUpModule():
158162
thread_info = threading_helper.threading_setup()

crates/vm/src/builtins/module.rs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,14 @@ impl Py<PyModule> {
160160
let mod_name_str = mod_name_obj
161161
.as_ref()
162162
.and_then(|n| n.downcast_ref::<PyStr>().map(|s| s.as_str().to_owned()));
163-
let mod_display = mod_name_str.as_deref().unwrap_or("<unknown module name>");
163+
164+
// If __name__ is not set or not a string, use a simpler error message
165+
let mod_display = match mod_name_str.as_deref() {
166+
Some(s) => s,
167+
None => {
168+
return Err(vm.new_attribute_error(format!("module has no attribute '{name}'")));
169+
}
170+
};
164171

165172
let spec = dict
166173
.get_item_opt(vm.ctx.intern_str("__spec__"), vm)
@@ -219,9 +226,9 @@ impl Py<PyModule> {
219226
}
220227
} else {
221228
// Check for uninitialized submodule
222-
let submod_initializing =
229+
let submodule_initializing =
223230
is_uninitialized_submodule(mod_name_str.as_ref(), name, vm);
224-
if submod_initializing {
231+
if submodule_initializing {
225232
Err(vm.new_attribute_error(format!(
226233
"cannot access submodule '{name}' of module '{mod_display}' \
227234
(most likely due to a circular import)"
@@ -428,8 +435,8 @@ impl GetAttr for PyModule {
428435
impl Representable for PyModule {
429436
#[inline]
430437
fn repr(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyStrRef> {
431-
let importlib = vm.import("_frozen_importlib", 0)?;
432-
let module_repr = importlib.get_attr("_module_repr", vm)?;
438+
// Use cached importlib reference (like interp->importlib)
439+
let module_repr = vm.importlib.get_attr("_module_repr", vm)?;
433440
let repr = module_repr.call((zelf.to_owned(),), vm)?;
434441
repr.downcast()
435442
.map_err(|_| vm.new_type_error("_module_repr did not return a string"))

crates/vm/src/import.rs

Lines changed: 234 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
33
use crate::{
44
AsObject, Py, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject,
5-
builtins::{PyCode, PyStr, list, traceback::PyTraceback},
5+
builtins::{PyCode, PyStr, PyStrRef, list, traceback::PyTraceback},
66
exceptions::types::PyBaseException,
77
scope::Scope,
88
vm::{VirtualMachine, resolve_frozen_alias, thread},
@@ -30,6 +30,7 @@ pub(crate) fn init_importlib_base(vm: &mut VirtualMachine) -> PyResult<PyObjectR
3030
Ok(bootstrap)
3131
})?;
3232
vm.import_func = importlib.get_attr(identifier!(vm, __import__), vm)?;
33+
vm.importlib = importlib.clone();
3334
Ok(importlib)
3435
}
3536

@@ -340,3 +341,235 @@ pub(crate) fn is_stdlib_module_name(name: &PyObjectRef, vm: &VirtualMachine) ->
340341
let result = vm.call_method(&stdlib_names, "__contains__", (name.clone(),))?;
341342
result.try_to_bool(vm)
342343
}
344+
345+
/// PyImport_ImportModuleLevelObject
346+
pub fn import_module_level(
347+
name: &PyStr,
348+
globals: Option<PyObjectRef>,
349+
fromlist: Option<PyObjectRef>,
350+
level: i32,
351+
vm: &VirtualMachine,
352+
) -> PyResult {
353+
if level < 0 {
354+
return Err(vm.new_value_error("level must be >= 0".to_owned()));
355+
}
356+
357+
let name_str = name.as_str();
358+
359+
// Resolve absolute name
360+
let abs_name = if level > 0 {
361+
// When globals is not provided (Rust None), raise KeyError
362+
// matching resolve_name() where globals==NULL
363+
if globals.is_none() {
364+
return Err(vm.new_key_error(
365+
vm.ctx.new_str("'__name__' not in globals").into(),
366+
));
367+
}
368+
let globals_ref = globals.as_ref().unwrap();
369+
// When globals is Python None, treat like empty mapping
370+
let empty_dict_obj;
371+
let globals_ref = if vm.is_none(globals_ref) {
372+
empty_dict_obj = vm.ctx.new_dict().into();
373+
&empty_dict_obj
374+
} else {
375+
globals_ref
376+
};
377+
let package = calc_package(Some(globals_ref), vm)?;
378+
if package.is_empty() {
379+
return Err(vm.new_import_error(
380+
"attempted relative import with no known parent package".to_owned(),
381+
vm.ctx.new_str(""),
382+
));
383+
}
384+
resolve_name(name_str, &package, level as usize, vm)?
385+
} else {
386+
if name_str.is_empty() {
387+
return Err(vm.new_value_error("Empty module name".to_owned()));
388+
}
389+
name_str.to_owned()
390+
};
391+
392+
// import_get_module + import_find_and_load
393+
let sys_modules = vm.sys_module.get_attr("modules", vm)?;
394+
let module = match sys_modules.get_item(&*abs_name, vm) {
395+
Ok(m) if !vm.is_none(&m) => m,
396+
_ => {
397+
let find_and_load = vm.importlib.get_attr("_find_and_load", vm)?;
398+
let abs_name_obj = vm.ctx.new_str(&*abs_name);
399+
find_and_load.call((abs_name_obj, vm.import_func.clone()), vm)?
400+
}
401+
};
402+
403+
// Handle fromlist
404+
let has_from = fromlist
405+
.as_ref()
406+
.filter(|fl| !vm.is_none(fl))
407+
.and_then(|fl| fl.clone().try_to_bool(vm).ok())
408+
.unwrap_or(false);
409+
410+
if has_from {
411+
let fromlist = fromlist.unwrap();
412+
// Only call _handle_fromlist if the module looks like a package
413+
// (has __path__). Non-module objects without __name__/__path__ would
414+
// crash inside _handle_fromlist; IMPORT_FROM handles per-attribute
415+
// errors with proper ImportError conversion.
416+
let has_path = vm
417+
.get_attribute_opt(module.clone(), vm.ctx.intern_str("__path__"))?
418+
.is_some();
419+
if has_path {
420+
let handle_fromlist = vm.importlib.get_attr("_handle_fromlist", vm)?;
421+
handle_fromlist.call((module, fromlist, vm.import_func.clone()), vm)
422+
} else {
423+
Ok(module)
424+
}
425+
} else if level == 0 || !name_str.is_empty() {
426+
match name_str.find('.') {
427+
None => Ok(module),
428+
Some(dot) => {
429+
let to_return = if level == 0 {
430+
name_str[..dot].to_owned()
431+
} else {
432+
let cut_off = name_str.len() - dot;
433+
abs_name[..abs_name.len() - cut_off].to_owned()
434+
};
435+
match sys_modules.get_item(&*to_return, vm) {
436+
Ok(m) => Ok(m),
437+
Err(_) if level == 0 => {
438+
// For absolute imports (level 0), try importing the
439+
// parent. Matches _bootstrap.__import__ behavior.
440+
let find_and_load = vm.importlib.get_attr("_find_and_load", vm)?;
441+
let to_return_obj = vm.ctx.new_str(&*to_return);
442+
find_and_load.call((to_return_obj, vm.import_func.clone()), vm)
443+
}
444+
Err(_) => {
445+
// For relative imports (level > 0), raise KeyError
446+
let to_return_obj: PyObjectRef = vm
447+
.ctx
448+
.new_str(format!(
449+
"'{to_return}' not in sys.modules as expected"
450+
))
451+
.into();
452+
Err(vm.new_key_error(to_return_obj))
453+
}
454+
}
455+
}
456+
}
457+
} else {
458+
Ok(module)
459+
}
460+
}
461+
462+
/// resolve_name in import.c - resolve relative import name
463+
fn resolve_name(name: &str, package: &str, level: usize, vm: &VirtualMachine) -> PyResult<String> {
464+
// Python: bits = package.rsplit('.', level - 1)
465+
// Rust: rsplitn(level, '.') gives maxsplit=level-1
466+
let parts: Vec<&str> = package.rsplitn(level, '.').collect();
467+
if parts.len() < level {
468+
return Err(vm.new_import_error(
469+
"attempted relative import beyond top-level package".to_owned(),
470+
vm.ctx.new_str(name),
471+
));
472+
}
473+
// rsplitn returns parts right-to-left, so last() is the leftmost (base)
474+
let base = parts.last().unwrap();
475+
if name.is_empty() {
476+
Ok(base.to_string())
477+
} else {
478+
Ok(format!("{base}.{name}"))
479+
}
480+
}
481+
482+
/// _calc___package__ - calculate package from globals for relative imports
483+
fn calc_package(globals: Option<&PyObjectRef>, vm: &VirtualMachine) -> PyResult<String> {
484+
let globals = globals.ok_or_else(|| {
485+
vm.new_import_error(
486+
"attempted relative import with no known parent package".to_owned(),
487+
vm.ctx.new_str(""),
488+
)
489+
})?;
490+
491+
let package = globals.get_item("__package__", vm).ok();
492+
let spec = globals.get_item("__spec__", vm).ok();
493+
494+
if let Some(ref pkg) = package
495+
&& !vm.is_none(pkg)
496+
{
497+
let pkg_str: PyStrRef = pkg.clone().downcast().map_err(|_| {
498+
vm.new_type_error("package must be a string".to_owned())
499+
})?;
500+
// Warn if __package__ != __spec__.parent
501+
if let Some(ref spec) = spec
502+
&& !vm.is_none(spec)
503+
&& let Ok(parent) = spec.get_attr("parent", vm)
504+
&& !pkg_str.is(&parent)
505+
&& pkg_str
506+
.as_object()
507+
.rich_compare_bool(&parent, crate::types::PyComparisonOp::Ne, vm)
508+
.unwrap_or(false)
509+
{
510+
let parent_repr = parent
511+
.repr(vm)
512+
.map(|s| s.as_str().to_owned())
513+
.unwrap_or_default();
514+
let msg = format!(
515+
"__package__ != __spec__.parent ('{}' != {})",
516+
pkg_str.as_str(),
517+
parent_repr
518+
);
519+
let warn = vm
520+
.import("_warnings", 0)
521+
.and_then(|w| w.get_attr("warn", vm));
522+
if let Ok(warn_fn) = warn {
523+
let _ = warn_fn.call(
524+
(
525+
vm.ctx.new_str(msg),
526+
vm.ctx.exceptions.deprecation_warning.to_owned(),
527+
),
528+
vm,
529+
);
530+
}
531+
}
532+
return Ok(pkg_str.as_str().to_owned());
533+
} else if let Some(ref spec) = spec
534+
&& !vm.is_none(spec)
535+
&& let Ok(parent) = spec.get_attr("parent", vm)
536+
&& !vm.is_none(&parent)
537+
{
538+
let parent_str: PyStrRef = parent.downcast().map_err(|_| {
539+
vm.new_type_error("package set to non-string".to_owned())
540+
})?;
541+
return Ok(parent_str.as_str().to_owned());
542+
}
543+
544+
// Fall back to __name__ and __path__
545+
let warn = vm.import("_warnings", 0).and_then(|w| w.get_attr("warn", vm));
546+
if let Ok(warn_fn) = warn {
547+
let _ = warn_fn.call(
548+
(
549+
vm.ctx.new_str("can't resolve package from __spec__ or __package__, falling back on __name__ and __path__"),
550+
vm.ctx.exceptions.import_warning.to_owned(),
551+
),
552+
vm,
553+
);
554+
}
555+
556+
let mod_name = globals.get_item("__name__", vm).map_err(|_| {
557+
vm.new_import_error(
558+
"attempted relative import with no known parent package".to_owned(),
559+
vm.ctx.new_str(""),
560+
)
561+
})?;
562+
let mod_name_str: PyStrRef = mod_name.downcast().map_err(|_| {
563+
vm.new_type_error("__name__ must be a string".to_owned())
564+
})?;
565+
let mut package = mod_name_str.as_str().to_owned();
566+
// If not a package (no __path__), strip last component.
567+
// Uses rpartition('.')[0] semantics: returns empty string when no dot.
568+
if globals.get_item("__path__", vm).is_err() {
569+
package = match package.rfind('.') {
570+
Some(dot) => package[..dot].to_owned(),
571+
None => String::new(),
572+
};
573+
}
574+
Ok(package)
575+
}

crates/vm/src/stdlib/builtins.rs

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -991,9 +991,30 @@ mod builtins {
991991
Ok(sum)
992992
}
993993

994+
#[derive(FromArgs)]
995+
struct ImportArgs {
996+
#[pyarg(any)]
997+
name: PyStrRef,
998+
#[pyarg(any, default)]
999+
globals: Option<PyObjectRef>,
1000+
#[allow(dead_code)]
1001+
#[pyarg(any, default)]
1002+
locals: Option<PyObjectRef>,
1003+
#[pyarg(any, default)]
1004+
fromlist: Option<PyObjectRef>,
1005+
#[pyarg(any, default)]
1006+
level: i32,
1007+
}
1008+
9941009
#[pyfunction]
995-
fn __import__(args: FuncArgs, vm: &VirtualMachine) -> PyResult {
996-
vm.import_func.call(args, vm)
1010+
fn __import__(args: ImportArgs, vm: &VirtualMachine) -> PyResult {
1011+
crate::import::import_module_level(
1012+
&args.name,
1013+
args.globals,
1014+
args.fromlist,
1015+
args.level,
1016+
vm,
1017+
)
9971018
}
9981019

9991020
#[pyfunction]

0 commit comments

Comments
 (0)