The pipeline is orchestrated through runner::process_source_map() (shared by main.rs and commands/run_tree.rs):
- Load sources - Parse the source DB JSON, load stubs, build
Sources(source_map.rs) - Build import graph + exports - Extract import relationships and module exports in a single pass (
ImportGraph::make_with_exports) - Analyze modules - Parallel per-module analysis to detect side effects (
project::run_analysis) - Generate output - Compute import chains and safety verdicts (
LifeGuardAnalysis::new)
AST parsing is on-demand — modules are parsed as needed during import graph construction and analysis, avoiding holding all ASTs in memory at once.
source_map.rsloads the source DB and provides theModuleProvidertrait for on-demand parsingimports.rsbuilds theImportGraphand collectsExportsin a single pass over all modulesproject.rsruns parallel per-module analysis (dispatching throughanalyzer.rstosource_analyzer.rsorstub_analyzer.rs), then merges results intoProjectInfoand computes safety verdictsoutput.rswalks the import graph to build the finalLifeGuardOutput(lazy_eligible dict + load_imports_eagerly set)
Analysis core:
source_analyzer.rs- Main analysis engine for.pyfiles, side-effect detectionstub_analyzer.rs- Analyzer for.pyistub files, parses effect annotationsproject.rs- Global analysis coordination, call graph traversal, safety verdictsmodule_info.rs- Combined DefinitionTable + ClassTable construction (single AST pass optimization)
Pipeline orchestration:
runner.rs- Shared pipeline orchestration used bymain.rsandcommands/run_tree.rs
AST traversal helpers:
cursor.rs- Tracks current scope during AST traversal (module → class → function)bindings.rs- Name resolution across scopes (BindingsTable)imports.rs- Import graph construction and resolution
Effect tracking:
effects.rs- Effect types and EffectTablemodule_effects.rs- Per-module effect accumulation (ModuleEffects)
Error and formatting:
errors.rs- Safety error types. These represent incompatibilities with Lazy Importsmodule_safety.rs- Per-module safety results (errors, force_imports_eager_overrides, implicit_imports)
Supporting infrastructure:
source_map.rs- Buck source DB loading, parallel AST parsing with rayonclass.rs- Class metadata extractionexports.rs- Module export detectionstubs.rs- Bundled stub file supportoutput.rs-LifeGuardOutputandLifeGuardAnalysisconstruction
Utilities:
builtins.rs- Builtin function resolution (e.g.,list,open,eval)graph.rs- Generic directed graph wrappingpetgraph::DiGraph, cycle detection via Tarjan's SCCmanual_override.rs- Hardcoded list of functions declared safemodule_parser.rs- Module parsing abstractiontracing.rs- Simple timing utilitytraits.rs- Extension traits bridging lifeguard with pyrefly typesfind_sources.rs- Seeds from.pyfiles under a directory and follows imports (optionally into site-packages); shared byrun-treeandgen-source-db
Binary utilities:
commands/run_tree.rs- Subcommand to analyze a directory tree without Buckcommands/show_effects.rs- Subcommand to dump effects for a single Python filecommands/gen_source_db.rs- Subcommand to generate a source DB from a directory tree
Local pyrefly forks:
pyrefly/definitions.rs- Local fork of pyrefly's definitions module (withLIFEGUARD:markers)pyrefly/globals.rs- Local fork of pyrefly's globals module
These are critical design decisions affecting correctness:
- Indexing imported objects: Treated as SAFE (most don't override
__getitem__unsafely) - Recursive function calls: Treated as UNSAFE (cannot determine termination)
- Unresolved function calls: Treated as SAFE (most are builtins)
exec()calls: Module marked as UNSAFE and added to load_imports_eagerly set (differs from original analyzer)sys.modulesaccess: Module added to load_imports_eagerly set (subscript access and method calls depend on import ordering that lazy imports disrupts)
The analyzer produces a LifeGuardOutput with two main components:
-
lazy_eligible(HashMap): Maps safe modules → set of failing dependencies that must be loaded eagerly- This is the primary mechanism for controlling lazy import behavior
- If module A is safe but imports module B which is unsafe, A maps to {B}
- The lazy import loader uses this to eagerly load specific imports within otherwise-safe modules
-
load_imports_eagerly(Set): Modules where ALL imports must be loaded eagerly- This is used for specific corner cases where the module's own imports must have already executed:
CustomFinalizer- classes with custom__del__implementations (unpredictable execution timing)ExecCall- modules that callexec()(negates static analysis)SysModulesAccess- modules that accesssys.modules(depends on other imports already being insys.modules)
- Do NOT use
load_imports_eagerlyfor general "unsafe module" handling - that's what thelazy_eligibledict is for - The
load_imports_eagerlyset tells the loader to completely disable lazy import behavior for that module's imports
- This is used for specific corner cases where the module's own imports must have already executed:
Key distinction: To mark a third-party module as unsafe for lazy imports, it should appear in the lazy_eligible dict values (as a failing dependency), NOT in the load_imports_eagerly set.
Import cycle handling: Import cycles are detected and handled — modules in import cycles have all cycle members added to their lazy_eligible set.