Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Lib/__hello_only__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
initialized = True
print("Hello world!")
2 changes: 0 additions & 2 deletions Lib/test/test_frozen.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ def test_frozen(self):
__hello__.main()
self.assertEqual(out.getvalue(), 'Hello world!\n')

@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: unexpectedly identical: <class '_frozen_importlib.FrozenImporter'>
def test_frozen_submodule_in_unfrozen_package(self):
with import_helper.CleanImport('__phello__', '__phello__.spam'):
with import_helper.frozen_modules(enabled=False):
Expand All @@ -40,7 +39,6 @@ def test_frozen_submodule_in_unfrozen_package(self):
self.assertIs(spam.__spec__.loader,
importlib.machinery.FrozenImporter)

@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: unexpectedly identical: <class '_frozen_importlib.FrozenImporter'>
def test_unfrozen_submodule_in_frozen_package(self):
with import_helper.CleanImport('__phello__', '__phello__.spam'):
with import_helper.frozen_modules(enabled=True):
Expand Down
9 changes: 4 additions & 5 deletions Lib/test/test_import/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -768,7 +768,7 @@ def run():
finally:
del sys.path[0]

@unittest.expectedFailure # TODO: RUSTPYTHON; FileNotFoundError: [WinError 2] No such file or directory: 'built-in'
@unittest.expectedFailure # TODO: RUSTPYTHON; no C extension support
@unittest.skipUnless(sys.platform == "win32", "Windows-specific")
def test_dll_dependency_import(self):
from _winapi import GetModuleFileName
Expand Down Expand Up @@ -814,7 +814,6 @@ def test_dll_dependency_import(self):
env=env,
cwd=os.path.dirname(pyexe))

@unittest.expectedFailure # TODO: RUSTPYTHON; _imp.get_frozen_object("x", b"6\'\xd5Cu\x12"). TypeError: expected at most 1 arguments, got 2
def test_issue105979(self):
# this used to crash
with self.assertRaises(ImportError) as cm:
Expand Down Expand Up @@ -1239,7 +1238,8 @@ def test_script_shadowing_stdlib_sys_path_modification(self):
stdout, stderr = popen.communicate()
self.assertRegex(stdout, expected_error)

@unittest.skip("TODO: RUSTPYTHON; AttributeError: module \"_imp\" has no attribute \"create_dynamic\"")
# TODO: RUSTPYTHON: _imp.create_dynamic is for C extensions, not applicable
@unittest.skip("TODO: RustPython _imp.create_dynamic not implemented")
def test_create_dynamic_null(self):
with self.assertRaisesRegex(ValueError, 'embedded null character'):
class Spec:
Expand Down Expand Up @@ -1398,7 +1398,6 @@ def test_basics(self):
self.assertEqual(mod.code_filename, self.file_name)
self.assertEqual(mod.func_filename, self.file_name)

@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: 'another_module.py' != .../unlikely_module_name.py
def test_incorrect_code_name(self):
py_compile.compile(self.file_name, dfile="another_module.py")
mod = self.import_module()
Expand Down Expand Up @@ -2045,6 +2044,7 @@ def exec_module(*args):
else:
importlib.SourceLoader.exec_module = old_exec_module

@unittest.expectedFailure # TODO: RUSTPYTHON; subprocess fails on Windows
@unittest.skipUnless(TESTFN_UNENCODABLE, 'need TESTFN_UNENCODABLE')
def test_unencodable_filename(self):
# Issue #11619: The Python parser and the import machinery must not
Expand Down Expand Up @@ -2095,7 +2095,6 @@ def test_rebinding(self):
from test.test_import.data.circular_imports.subpkg import util
self.assertIs(util.util, rebinding.util)

@unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module "test.test_import.data.circular_imports" has no attribute "binding"
def test_binding(self):
try:
import test.test_import.data.circular_imports.binding
Expand Down
1 change: 1 addition & 0 deletions Lib/test/test_importlib/frozen/test_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from test.support import captured_stdout, import_helper, STDLIB_DIR
import contextlib
import os.path
import sys
import types
import unittest
import warnings
Expand Down
4 changes: 4 additions & 0 deletions Lib/test/test_importlib/test_locks.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,10 @@ def test_all_locks(self):
Source_LifetimeTests
) = test_util.test_both(LifetimeTests, init=init)

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


def setUpModule():
thread_info = threading_helper.threading_setup()
Expand Down
1 change: 1 addition & 0 deletions Lib/test/test_importlib/test_windows.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ def test_raises_deprecation_warning(self):

@unittest.skipUnless(sys.platform.startswith('win'), 'requires Windows')
class WindowsExtensionSuffixTests:
@unittest.expectedFailure # TODO: RUSTPYTHON; no C extension (.pyd) support
def test_tagged_suffix(self):
suffixes = self.machinery.EXTENSION_SUFFIXES
abi_flags = "t" if support.Py_GIL_DISABLED else ""
Expand Down
5 changes: 4 additions & 1 deletion Lib/test/test_importlib/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@
import tempfile
import types

_testsinglephase = import_helper.import_module("_testsinglephase")
try:
_testsinglephase = import_helper.import_module("_testsinglephase")
except unittest.SkipTest:
_testsinglephase = None # TODO: RUSTPYTHON


BUILTINS = types.SimpleNamespace()
Expand Down
14 changes: 11 additions & 3 deletions crates/codegen/src/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2065,11 +2065,19 @@ impl Compiler {
let idx = self.name(&name.name);
emit!(self, Instruction::ImportName { idx });
if let Some(alias) = &name.asname {
for part in name.name.split('.').skip(1) {
let parts: Vec<&str> = name.name.split('.').skip(1).collect();
for (i, part) in parts.iter().enumerate() {
let idx = self.name(part);
self.emit_load_attr(idx);
emit!(self, Instruction::ImportFrom { idx });
if i < parts.len() - 1 {
emit!(self, Instruction::Swap { index: 2 });
emit!(self, Instruction::PopTop);
}
}
self.store_name(alias.as_str())?;
if !parts.is_empty() {
emit!(self, Instruction::PopTop);
}
self.store_name(alias.as_str())?
} else {
self.store_name(name.name.split('.').next().unwrap())?
}
Expand Down
1 change: 1 addition & 0 deletions crates/vm/Lib/python_builtins/__hello_only__.py
132 changes: 119 additions & 13 deletions crates/vm/src/builtins/module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::{
class::PyClassImpl,
convert::ToPyObject,
function::{FuncArgs, PyMethodDef, PySetterValue},
import::{get_spec_file_origin, is_possibly_shadowing_path, is_stdlib_module_name},
types::{GetAttr, Initializer, Representable},
};

Expand Down Expand Up @@ -152,20 +153,96 @@ impl Py<PyModule> {
if let Ok(getattr) = self.dict().get_item(identifier!(vm, __getattr__), vm) {
return getattr.call((name.to_owned(),), vm);
}
let module_name = if let Some(name) = self.name(vm) {
format!(" '{name}'")
let dict = self.dict();

// Get the raw __name__ object (may be a str subclass)
let mod_name_obj = dict
.get_item_opt(identifier!(vm, __name__), vm)
.ok()
.flatten();
let mod_name_str = mod_name_obj
.as_ref()
.and_then(|n| n.downcast_ref::<PyStr>().map(|s| s.as_str().to_owned()));

// If __name__ is not set or not a string, use a simpler error message
let mod_display = match mod_name_str.as_deref() {
Some(s) => s,
None => {
return Err(vm.new_attribute_error(format!("module has no attribute '{name}'")));
}
};

let spec = dict
.get_item_opt(vm.ctx.intern_str("__spec__"), vm)
.ok()
.flatten()
.filter(|s| !vm.is_none(s));

let origin = get_spec_file_origin(&spec, vm);

let is_possibly_shadowing = origin
.as_ref()
.map(|o| is_possibly_shadowing_path(o, vm))
.unwrap_or(false);
// Use the ORIGINAL __name__ object for stdlib check (may raise TypeError
// if __name__ is an unhashable str subclass)
let is_possibly_shadowing_stdlib = if is_possibly_shadowing {
if let Some(ref mod_name) = mod_name_obj {
is_stdlib_module_name(mod_name, vm)?
} else {
false
}
} else {
"".to_owned()
false
};
Err(vm.new_attribute_error(format!("module{module_name} has no attribute '{name}'")))
}

fn name(&self, vm: &VirtualMachine) -> Option<PyStrRef> {
let name = self
.as_object()
.generic_getattr_opt(identifier!(vm, __name__), None, vm)
.unwrap_or_default()?;
name.downcast::<PyStr>().ok()
if is_possibly_shadowing_stdlib {
let origin = origin.as_ref().unwrap();
Err(vm.new_attribute_error(format!(
"module '{mod_display}' has no attribute '{name}' \
(consider renaming '{origin}' since it has the same \
name as the standard library module named '{mod_display}' \
and prevents importing that standard library module)"
)))
} else {
let is_initializing = PyModule::is_initializing(&dict, vm);
if is_initializing {
if is_possibly_shadowing {
let origin = origin.as_ref().unwrap();
Err(vm.new_attribute_error(format!(
"module '{mod_display}' has no attribute '{name}' \
(consider renaming '{origin}' if it has the same name \
as a library you intended to import)"
)))
} else if let Some(ref origin) = origin {
Err(vm.new_attribute_error(format!(
"partially initialized module '{mod_display}' from '{origin}' \
has no attribute '{name}' \
(most likely due to a circular import)"
)))
} else {
Err(vm.new_attribute_error(format!(
"partially initialized module '{mod_display}' \
has no attribute '{name}' \
(most likely due to a circular import)"
)))
}
} else {
// Check for uninitialized submodule
let submodule_initializing =
is_uninitialized_submodule(mod_name_str.as_ref(), name, vm);
if submodule_initializing {
Err(vm.new_attribute_error(format!(
"cannot access submodule '{name}' of module '{mod_display}' \
(most likely due to a circular import)"
)))
} else {
Err(vm.new_attribute_error(format!(
"module '{mod_display}' has no attribute '{name}'"
)))
}
}
}
}

// TODO: to be replaced by the commented-out dict method above once dictoffset land
Expand Down Expand Up @@ -361,8 +438,8 @@ impl GetAttr for PyModule {
impl Representable for PyModule {
#[inline]
fn repr(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyStrRef> {
let importlib = vm.import("_frozen_importlib", 0)?;
let module_repr = importlib.get_attr("_module_repr", vm)?;
// Use cached importlib reference (like interp->importlib)
let module_repr = vm.importlib.get_attr("_module_repr", vm)?;
let repr = module_repr.call((zelf.to_owned(),), vm)?;
repr.downcast()
.map_err(|_| vm.new_type_error("_module_repr did not return a string"))
Expand All @@ -377,3 +454,32 @@ impl Representable for PyModule {
pub(crate) fn init(context: &Context) {
PyModule::extend_class(context, context.types.module_type);
}

/// Check if {module_name}.{name} is an uninitialized submodule in sys.modules.
fn is_uninitialized_submodule(
module_name: Option<&String>,
name: &Py<PyStr>,
vm: &VirtualMachine,
) -> bool {
let mod_name = match module_name {
Some(n) => n.as_str(),
None => return false,
};
let full_name = format!("{mod_name}.{name}");
let sys_modules = match vm.sys_module.get_attr("modules", vm).ok() {
Some(m) => m,
None => return false,
};
let sub_mod = match sys_modules.get_item(&full_name, vm).ok() {
Some(m) => m,
None => return false,
};
let spec = match sub_mod.get_attr("__spec__", vm).ok() {
Some(s) if !vm.is_none(&s) => s,
_ => return false,
};
spec.get_attr("_initializing", vm)
.ok()
.and_then(|v| v.try_to_bool(vm).ok())
.unwrap_or(false)
}
Loading
Loading