@@ -5,6 +5,7 @@ use crate::{
55 class:: PyClassImpl ,
66 convert:: ToPyObject ,
77 function:: { FuncArgs , PyMethodDef , PySetterValue } ,
8+ import:: { get_spec_file_origin, is_possibly_shadowing_path, is_stdlib_module_name} ,
89 types:: { GetAttr , Initializer , Representable } ,
910} ;
1011
@@ -152,20 +153,96 @@ impl Py<PyModule> {
152153 if let Ok ( getattr) = self . dict ( ) . get_item ( identifier ! ( vm, __getattr__) , vm) {
153154 return getattr. call ( ( name. to_owned ( ) , ) , vm) ;
154155 }
155- let module_name = if let Some ( name) = self . name ( vm) {
156- format ! ( " '{name}'" )
156+ let dict = self . dict ( ) ;
157+
158+ // Get the raw __name__ object (may be a str subclass)
159+ let mod_name_obj = dict
160+ . get_item_opt ( identifier ! ( vm, __name__) , vm)
161+ . ok ( )
162+ . flatten ( ) ;
163+ let mod_name_str = mod_name_obj
164+ . as_ref ( )
165+ . and_then ( |n| n. downcast_ref :: < PyStr > ( ) . map ( |s| s. as_str ( ) . to_owned ( ) ) ) ;
166+
167+ // If __name__ is not set or not a string, use a simpler error message
168+ let mod_display = match mod_name_str. as_deref ( ) {
169+ Some ( s) => s,
170+ None => {
171+ return Err ( vm. new_attribute_error ( format ! ( "module has no attribute '{name}'" ) ) ) ;
172+ }
173+ } ;
174+
175+ let spec = dict
176+ . get_item_opt ( vm. ctx . intern_str ( "__spec__" ) , vm)
177+ . ok ( )
178+ . flatten ( )
179+ . filter ( |s| !vm. is_none ( s) ) ;
180+
181+ let origin = get_spec_file_origin ( & spec, vm) ;
182+
183+ let is_possibly_shadowing = origin
184+ . as_ref ( )
185+ . map ( |o| is_possibly_shadowing_path ( o, vm) )
186+ . unwrap_or ( false ) ;
187+ // Use the ORIGINAL __name__ object for stdlib check (may raise TypeError
188+ // if __name__ is an unhashable str subclass)
189+ let is_possibly_shadowing_stdlib = if is_possibly_shadowing {
190+ if let Some ( ref mod_name) = mod_name_obj {
191+ is_stdlib_module_name ( mod_name, vm) ?
192+ } else {
193+ false
194+ }
157195 } else {
158- "" . to_owned ( )
196+ false
159197 } ;
160- Err ( vm. new_attribute_error ( format ! ( "module{module_name} has no attribute '{name}'" ) ) )
161- }
162198
163- fn name ( & self , vm : & VirtualMachine ) -> Option < PyStrRef > {
164- let name = self
165- . as_object ( )
166- . generic_getattr_opt ( identifier ! ( vm, __name__) , None , vm)
167- . unwrap_or_default ( ) ?;
168- name. downcast :: < PyStr > ( ) . ok ( )
199+ if is_possibly_shadowing_stdlib {
200+ let origin = origin. as_ref ( ) . unwrap ( ) ;
201+ Err ( vm. new_attribute_error ( format ! (
202+ "module '{mod_display}' has no attribute '{name}' \
203+ (consider renaming '{origin}' since it has the same \
204+ name as the standard library module named '{mod_display}' \
205+ and prevents importing that standard library module)"
206+ ) ) )
207+ } else {
208+ let is_initializing = PyModule :: is_initializing ( & dict, vm) ;
209+ if is_initializing {
210+ if is_possibly_shadowing {
211+ let origin = origin. as_ref ( ) . unwrap ( ) ;
212+ Err ( vm. new_attribute_error ( format ! (
213+ "module '{mod_display}' has no attribute '{name}' \
214+ (consider renaming '{origin}' if it has the same name \
215+ as a library you intended to import)"
216+ ) ) )
217+ } else if let Some ( ref origin) = origin {
218+ Err ( vm. new_attribute_error ( format ! (
219+ "partially initialized module '{mod_display}' from '{origin}' \
220+ has no attribute '{name}' \
221+ (most likely due to a circular import)"
222+ ) ) )
223+ } else {
224+ Err ( vm. new_attribute_error ( format ! (
225+ "partially initialized module '{mod_display}' \
226+ has no attribute '{name}' \
227+ (most likely due to a circular import)"
228+ ) ) )
229+ }
230+ } else {
231+ // Check for uninitialized submodule
232+ let submodule_initializing =
233+ is_uninitialized_submodule ( mod_name_str. as_ref ( ) , name, vm) ;
234+ if submodule_initializing {
235+ Err ( vm. new_attribute_error ( format ! (
236+ "cannot access submodule '{name}' of module '{mod_display}' \
237+ (most likely due to a circular import)"
238+ ) ) )
239+ } else {
240+ Err ( vm. new_attribute_error ( format ! (
241+ "module '{mod_display}' has no attribute '{name}'"
242+ ) ) )
243+ }
244+ }
245+ }
169246 }
170247
171248 // TODO: to be replaced by the commented-out dict method above once dictoffset land
@@ -361,8 +438,8 @@ impl GetAttr for PyModule {
361438impl Representable for PyModule {
362439 #[ inline]
363440 fn repr ( zelf : & Py < Self > , vm : & VirtualMachine ) -> PyResult < PyStrRef > {
364- let importlib = vm . import ( "_frozen_importlib" , 0 ) ? ;
365- let module_repr = importlib. get_attr ( "_module_repr" , vm) ?;
441+ // Use cached importlib reference (like interp->importlib)
442+ let module_repr = vm . importlib . get_attr ( "_module_repr" , vm) ?;
366443 let repr = module_repr. call ( ( zelf. to_owned ( ) , ) , vm) ?;
367444 repr. downcast ( )
368445 . map_err ( |_| vm. new_type_error ( "_module_repr did not return a string" ) )
@@ -377,3 +454,32 @@ impl Representable for PyModule {
377454pub ( crate ) fn init ( context : & Context ) {
378455 PyModule :: extend_class ( context, context. types . module_type ) ;
379456}
457+
458+ /// Check if {module_name}.{name} is an uninitialized submodule in sys.modules.
459+ fn is_uninitialized_submodule (
460+ module_name : Option < & String > ,
461+ name : & Py < PyStr > ,
462+ vm : & VirtualMachine ,
463+ ) -> bool {
464+ let mod_name = match module_name {
465+ Some ( n) => n. as_str ( ) ,
466+ None => return false ,
467+ } ;
468+ let full_name = format ! ( "{mod_name}.{name}" ) ;
469+ let sys_modules = match vm. sys_module . get_attr ( "modules" , vm) . ok ( ) {
470+ Some ( m) => m,
471+ None => return false ,
472+ } ;
473+ let sub_mod = match sys_modules. get_item ( & full_name, vm) . ok ( ) {
474+ Some ( m) => m,
475+ None => return false ,
476+ } ;
477+ let spec = match sub_mod. get_attr ( "__spec__" , vm) . ok ( ) {
478+ Some ( s) if !vm. is_none ( & s) => s,
479+ _ => return false ,
480+ } ;
481+ spec. get_attr ( "_initializing" , vm)
482+ . ok ( )
483+ . and_then ( |v| v. try_to_bool ( vm) . ok ( ) )
484+ . unwrap_or ( false )
485+ }
0 commit comments