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.
@@ -476,6 +551,56 @@ _PyImport_LoadLazyImportTstate(PyThreadState *tstate, PyObject *lazy_import)
476551 return lazy_import_resolve_impl (tstate , lazy_import , NULL );
477552}
478553
554+ // Resolve a pending child as an aliased import, preserving its declaration.
555+ PyObject *
556+ _PyLazyImport_LoadChild (PyThreadState * tstate , PyObject * declaration ,
557+ PyObject * name )
558+ {
559+ PyLazyImportObject * source = (PyLazyImportObject * )declaration ;
560+ PyObject * lz = _PyLazyImport_New (NULL , source -> lz_builtins , name , NULL );
561+ Py_ssize_t end = PyUnicode_GET_LENGTH (name );
562+ Py_ssize_t dot = PyUnicode_FindChar (name , '.' , 0 , end , 1 );
563+ if (dot == -2 ) {
564+ Py_CLEAR (lz );
565+ }
566+ while (lz != NULL && dot >= 0 ) {
567+ Py_ssize_t start = dot + 1 ;
568+ dot = PyUnicode_FindChar (name , '.' , start , end , 1 );
569+ if (dot == -2 ) {
570+ Py_CLEAR (lz );
571+ break ;
572+ }
573+ PyObject * attr = PyUnicode_Substring (name , start , dot < 0 ? end : dot );
574+ PyObject * next = attr == NULL ? NULL :
575+ _PyLazyImport_New (NULL , NULL , lz , attr );
576+ Py_XDECREF (attr );
577+ Py_SETREF (lz , next );
578+ }
579+ if (lz == NULL ) {
580+ return NULL ;
581+ }
582+ PyLazyImportObject * child = (PyLazyImportObject * )lz ;
583+ child -> lz_code = (PyCodeObject * )Py_XNewRef (source -> lz_code );
584+ child -> lz_instr_offset = source -> lz_instr_offset ;
585+ PyObject * result = _PyImport_LoadLazyImportTstate (tstate , lz );
586+ Py_DECREF (lz );
587+ if (result != NULL && PyModule_Check (result ) &&
588+ PyUnicode_Compare (source -> lz_from , name ) != 0 &&
589+ !PyDict_Contains (_PyModule_GetDict (result ), & _Py_ID (__path__ ))) {
590+ // Reaching a non-package must still reject an invalid dotted import.
591+ Py_DECREF (result );
592+ return _PyLazyImport_LoadChild (tstate , declaration , source -> lz_from );
593+ }
594+ return result ;
595+ }
596+
597+ int
598+ _PyLazyImport_IsActive (PyObject * declaration )
599+ {
600+ return FT_ATOMIC_LOAD_INT_RELAXED (
601+ ((PyLazyImportObject * )declaration )-> lz_active );
602+ }
603+
479604// Loading pkg.child can replace a placeholder in pkg.child with the module
480605// before a from-import retrieves the value that belongs in that binding.
481606// This is an optimization that can be safely skipped.
0 commit comments