99#include "pycore_lazyimportobject.h"
1010#include "pycore_long.h"
1111#include "pycore_moduleobject.h"
12+ #include "pycore_pyatomic_ft_wrappers.h"
1213#include "pycore_pyerrors.h"
1314#include "pycore_traceback.h"
1415#include "pycore_tstate.h"
@@ -25,6 +26,8 @@ typedef struct {
2526 // Declaration location.
2627 PyCodeObject * lz_code ;
2728 int lz_instr_offset ;
29+ // The root was accessed, so its declared path requires import semantics.
30+ int lz_active ;
2831} PyLazyImportObject ;
2932
3033#define PyLazyImportObject_CAST (op ) ((PyLazyImportObject *)(op))
@@ -70,6 +73,7 @@ _PyLazyImport_New(_PyInterpreterFrame *frame, PyObject *builtins,
7073
7174 m -> lz_code = NULL ;
7275 m -> lz_instr_offset = -1 ;
76+ m -> lz_active = 0 ;
7377
7478 if (frame != NULL ) {
7579 m -> lz_code = (PyCodeObject * )Py_NewRef (_PyFrame_GetCode (frame ));
@@ -429,10 +433,81 @@ lazy_import_resolve_impl(PyThreadState *tstate, PyObject *lazy_import,
429433 PyErr_SetString (PyExc_ImportError , "__import__ not found" );
430434 goto done ;
431435 }
436+ PyObject * name = Py_NewRef (root -> lz_from );
437+ if (first == NULL &&
438+ (fromlist == Py_None ||
439+ (PyTuple_Check (fromlist ) && PyTuple_GET_SIZE (fromlist ) == 0 )) &&
440+ _PyImport_IsDefaultImportFunc (tstate -> interp , import_func ) &&
441+ root -> lz_builtins == tstate -> interp -> builtins ) {
442+ // A plain import binds the root package. Its pending children are
443+ // imported separately when their attributes are accessed.
444+ Py_ssize_t dot = PyUnicode_FindChar (
445+ name , '.' , 0 , PyUnicode_GET_LENGTH (name ), 1 );
446+ if (dot >= 0 ) {
447+ // Preserve eager traversal through aliased or non-package parents.
448+ int regular = 1 ;
449+ int complete = 1 ;
450+ Py_ssize_t end = PyUnicode_GET_LENGTH (name );
451+ for (Py_ssize_t i = dot ; i >= 0 && regular ;
452+ i = PyUnicode_FindChar (name , '.' , i + 1 , end , 1 )) {
453+ PyObject * prefix = PyUnicode_Substring (name , 0 , i );
454+ PyObject * cached = prefix == NULL ? NULL :
455+ lazy_import_get_loaded_attr (tstate , prefix , & _Py_ID (__name__ ));
456+ if (cached == NULL ) {
457+ complete = 0 ;
458+ Py_XDECREF (prefix );
459+ break ;
460+ }
461+ PyObject * path = lazy_import_get_loaded_attr (
462+ tstate , prefix , & _Py_ID (__path__ ));
463+ regular = path != NULL && PyUnicode_Check (cached ) &&
464+ PyUnicode_Compare (cached , prefix ) == 0 ;
465+ Py_XDECREF (path );
466+ Py_DECREF (cached );
467+ Py_DECREF (prefix );
468+ }
469+ PyObject * loaded = lazy_import_get_loaded_attr (
470+ tstate , name , & _Py_ID (__name__ ));
471+ if (regular && (!complete || loaded == NULL ) && !PyErr_Occurred ()) {
472+ Py_SETREF (name , PyUnicode_Substring (name , 0 , dot ));
473+ }
474+ Py_XDECREF (loaded );
475+ if (PyErr_Occurred ()) {
476+ Py_CLEAR (name );
477+ }
478+ }
479+ else if (dot == -2 ) {
480+ Py_CLEAR (name );
481+ }
482+ if (name == NULL ) {
483+ goto done ;
484+ }
485+ }
432486 obj = _PyEval_ImportNameWithImport (
433487 tstate , import_func , globals , globals ,
434- root -> lz_from , fromlist , _PyLong_GetZero ()
488+ name , fromlist , _PyLong_GetZero ()
435489 );
490+ if (obj != NULL && name != root -> lz_from ) {
491+ FT_ATOMIC_STORE_INT_RELAXED (root -> lz_active , 1 );
492+ FT_ATOMIC_STORE_INT_RELAXED (tstate -> interp -> imports .has_lazy_submodules , 1 );
493+ int package = PyModule_CheckExact (obj ) ?
494+ PyDict_Contains (_PyModule_GetDict (obj ), & _Py_ID (__path__ )) : 0 ;
495+ if (package < 0 ) {
496+ Py_CLEAR (obj );
497+ }
498+ else if (!package ) {
499+ Py_DECREF (obj );
500+ obj = _PyEval_ImportNameWithImport (
501+ tstate , import_func , globals , globals ,
502+ root -> lz_from , fromlist , _PyLong_GetZero ());
503+ }
504+ }
505+ if (obj != NULL && name == root -> lz_from &&
506+ _PyImport_IsDefaultImportFunc (tstate -> interp , import_func ) &&
507+ _PyImport_ClearLazySubmodule (tstate , name , 0 ) < 0 ) {
508+ Py_CLEAR (obj );
509+ }
510+ Py_DECREF (name );
436511 obj = lazy_import_resolve_result (tstate , obj );
437512 // The normal importer may publish this module on its parent. Custom
438513 // hooks retain control of their own assignments to the parent.
@@ -453,7 +528,7 @@ lazy_import_resolve_impl(PyThreadState *tstate, PyObject *lazy_import,
453528 }
454529 assert (obj == NULL || !PyLazyImport_CheckExact (obj ));
455530 if (obj != NULL ) {
456- PyObject * name = lazy_import_name (lz );
531+ PyObject * name = lazy_import_path (lz );
457532 if (name == NULL ||
458533 _PyImport_DiscardLazyModule (tstate -> interp , name ) < 0 ) {
459534 Py_CLEAR (obj );
@@ -484,6 +559,58 @@ _PyImport_LoadLazyImportTstate(PyThreadState *tstate, PyObject *lazy_import)
484559 return lazy_import_resolve_impl (tstate , lazy_import , NULL );
485560}
486561
562+ // Resolve a pending child as an aliased import, preserving its declaration.
563+ PyObject *
564+ _PyLazyImport_LoadChild (PyThreadState * tstate , PyObject * declaration ,
565+ PyObject * name )
566+ {
567+ assert (PyLazyImport_CheckExact (declaration ));
568+ PyLazyImportObject * source = PyLazyImportObject_CAST (declaration );
569+ PyObject * lz = _PyLazyImport_New (NULL , source -> lz_builtins , name , NULL );
570+ Py_ssize_t end = PyUnicode_GET_LENGTH (name );
571+ Py_ssize_t dot = PyUnicode_FindChar (name , '.' , 0 , end , 1 );
572+ if (dot == -2 ) {
573+ Py_CLEAR (lz );
574+ }
575+ while (lz != NULL && dot >= 0 ) {
576+ Py_ssize_t start = dot + 1 ;
577+ dot = PyUnicode_FindChar (name , '.' , start , end , 1 );
578+ if (dot == -2 ) {
579+ Py_CLEAR (lz );
580+ break ;
581+ }
582+ PyObject * attr = PyUnicode_Substring (name , start , dot < 0 ? end : dot );
583+ PyObject * next = attr == NULL ? NULL :
584+ _PyLazyImport_New (NULL , NULL , lz , attr );
585+ Py_XDECREF (attr );
586+ Py_SETREF (lz , next );
587+ }
588+ if (lz == NULL ) {
589+ return NULL ;
590+ }
591+ PyLazyImportObject * child = PyLazyImportObject_CAST (lz );
592+ child -> lz_code = (PyCodeObject * )Py_XNewRef (source -> lz_code );
593+ child -> lz_instr_offset = source -> lz_instr_offset ;
594+ PyObject * result = _PyImport_LoadLazyImportTstate (tstate , lz );
595+ Py_DECREF (lz );
596+ if (result != NULL && PyModule_Check (result ) &&
597+ PyUnicode_Compare (source -> lz_from , name ) != 0 &&
598+ !PyDict_Contains (_PyModule_GetDict (result ), & _Py_ID (__path__ ))) {
599+ // Reaching a non-package must still reject an invalid dotted import.
600+ Py_DECREF (result );
601+ return _PyLazyImport_LoadChild (tstate , declaration , source -> lz_from );
602+ }
603+ return result ;
604+ }
605+
606+ int
607+ _PyLazyImport_IsActive (PyObject * declaration )
608+ {
609+ assert (PyLazyImport_CheckExact (declaration ));
610+ return FT_ATOMIC_LOAD_INT_RELAXED (
611+ PyLazyImportObject_CAST (declaration )-> lz_active );
612+ }
613+
487614// Loading pkg.child can replace a placeholder in pkg.child with the module
488615// before a from-import retrieves the value that belongs in that binding.
489616// This is an optimization that can be safely skipped.
0 commit comments