Keyboard shortcuts

Press or to navigate between chapters

Press ⌘K or Ctrl+K to search

Press ? to show this help

Press Esc to hide this help

Known Limitations & Scope Boundaries

Tags: everyone

Type System Limitations

Strato’s semantic resolution depends on the Strato facade over vendored Ruff/ty for module, name, type, call, property, and dunder facts. When a supported facade query returns Unknown for an individual expression, the call is skipped silently per the Precision Policy.

LimitationImpactMitigationStatus
No-annotation dynamic typesVariables whose type cannot be inferred by ty have unknown type. Method calls on those values are unresolvable.Add type hints or use @blocking decorator.v1 – by design
Heavily metaprogrammed codeClasses generated via metaclasses, type(), or __init_subclass__ are invisible to static analysis.Annotate generated methods with @blocking.v1 – out of scope
Runtime type constructiontype(name, bases, dict) creates classes at runtime. Strato cannot resolve calls to methods defined this way.Avoid runtime class construction in async contexts.v1 – out of scope
Plugin-based systemsFrameworks loading callables via entry points or plugin registries are invisible.Manually annotate plugin callables with @blocking.v1 – out of scope
Generic type parametersT in def process(x: T) -> T is not resolved. Method calls on x are unresolvable.Use concrete types or @blocking annotations.v1 – no generics support
Union typesx: Union[A, B] – Strato does not track which branch is active.Refactor to avoid unions in async contexts.v1 – no union tracking

Import System Limitations

Strato relies on vendored Ruff/ty for static import semantics under configured source roots, Python version, and stub paths mapped to ty environment.extra-paths. Dynamic or runtime-modified imports remain outside Strato’s guarantees.

LimitationImpactMitigationStatus
Dynamic importsimportlib.import_module(name) where name is computed at runtime.Use static imports in async contexts.v1 – unresolvable
Runtime import calls even with literal stringsStrato does not special-case importlib.import_module("myapp.utils") as a static import.Refactor to import myapp.utils.v1 – not implemented
.pth filessite-packages/*.pth files modify sys.path at runtime.Use explicit source roots in config.v1 – out of scope
Import hooksCustom sys.meta_path or sys.path_hooks importers.Use standard filesystem-based imports.v1 – out of scope
Conditional importsResolution follows ty’s static semantics; Strato does not execute both runtime branches.Use a single canonical import.v1 – best-effort
Star importsSupported only in happy-path cases where ty can statically enumerate the exported names.Use explicit imports.v1 – best-effort
Namespace packages (PEP 420)Happy-path first-party namespace packages can resolve under configured source roots; support otherwise depends on ty and stub/source-root configuration. External namespace packages are not a Strato guarantee.Add __init__.py or explicit source roots where possible.v1 – partial
Circular importsSymbols registered before bodies walked, but runtime ImportError not detected.Refactor to eliminate circular imports.v1 – no runtime validation

Call Graph Limitations

Strato builds a static call graph by analyzing function bodies. It cannot resolve calls that depend on runtime state or higher-order function patterns.

LimitationImpactMitigationStatus
Callbacks passed as argumentsdef process(callback): callback()callback unresolvable.Use @blocking on functions that invoke callbacks.v1 – unresolvable
Higher-order functions returning callableshandler = get_handler(); handler() – unresolvable.Annotate returned callables with @blocking.v1 – unresolvable
Decorator chains that transform signaturesDecorators that replace functions with wrappers – Strato analyzes the original function.Annotate wrappers with @blocking.v1 – original function only
Monkey-patchingMyClass.method = some_other_function – runtime reassignment invisible.Avoid monkey-patching in async contexts.v1 – original definition only
Generators and yieldGenerator bodies visited, but generator consumption (next(gen())) does not create call edge to body.Annotate blocking generators with @blocking.v1 – partial support
eval() / exec()String-based code execution invisible.Avoid in async contexts.v1 – out of scope
getattr() / setattr()Dynamic attribute access unresolvable.Use explicit attribute access.v1 – unresolvable
General functools.partial flowPartial application is not tracked as a general callable value outside recognized executor-wrapper arguments.Use direct calls or annotate/configure the wrapper that receives the callable.v1 – limited support

Scope Limitations

LimitationImpactMitigationStatus
asyncio-onlytrio, curio, and anyio framework semantics are not modeled in v1. Built-in escape hatches are asyncio-only.Use asyncio for v1, or mark project-specific safe boundaries explicitly.v1 – asyncio only (Async Library Support)
No runtime analysisCannot detect blocking calls conditionally skipped at runtime.Use runtime profiling tools to complement.v1 – static only
No inter-process analysisBlocking calls in subprocesses invisible.Subprocess code is isolated from event loop.v1 – out of scope
Single-project onlyDoes not traverse into installed third-party packages.Extend blocking database via config.v1 – first-party focus
No cross-package analysisMonorepo packages analyzed separately.Run Strato on each package independently.v1 – single-project only

“Skip Silently” Behavior

Strato follows a high-precision policy (Precision Policy): when it cannot definitively prove a call is blocking, it skips silently. This section documents every such case.

CaseBehaviorRationale
Unresolvable calleeFacade has no callable target → no call edge createdUnknown != Blocking
Unknown semantic target → no property/dunder edgeThe facade cannot resolve the property or dunder target → access not checkedCannot prove which callable would run
External symbol not in DBThird-party symbol without database entry → no phantom nodeOnly known-blocking third-party functions tracked
Unresolvable importDynamic import, import hook, runtime path mutation, or missing module → no bindingCannot analyze what is not available through static filesystem-backed import semantics
Star import with severe syntax errorsTarget module cannot provide a safe export set → no bindings from star importCannot enumerate symbols without reliable declarations
Decorator replacing functionOriginal function analyzed, not wrapperDecorators not executed statically
Callback parameter invokedcallback() inside function → unresolvableHigher-order requires interprocedural analysis
Conditional import branch not resolved by tyBinding unavailable to StratoBest-effort static semantics
Star import not statically enumerableExported names unavailable to StratoAvoid guessing imported names
Monkey-patched methodOriginal method analyzed, not patched replacementRuntime reassignments invisible to static analysis
eval() / exec() / getattr()String-based execution/access invisibleCannot statically analyze runtime-constructed code

User guidance: If Strato misses a blocking call, users can: (1) add type hints to improve resolution, (2) use @blocking to manually annotate, (3) refactor dynamic patterns to explicit calls.

Future Work (v2+)

FeatureDescriptionPriorityComplexity
trio/anyio/curio supportRecognize framework-specific escape hatchesHighMedium
Framework integrationDjango sync_to_async, FastAPI thread offloading, Celery task dispatchHighHigh
Dynamic analysis integrationRuntime profiling + static call graph correlationMediumHigh
Auto-fix suggestionsGenerate asyncio.to_thread wrapping or suggest async alternativesMediumMedium
IDE plugin / LSP serverReal-time diagnostics in editorsMediumHigh
Cross-package analysisTraverse into installed third-party packagesMediumHigh
Incremental graph updatesOnly rebuild affected subgraph on file changeLowHigh
Watch modeContinuous analysis on file saveLowLow
GitHub ActionPre-built CI integration: uses: strato-linter/strato-action@v1LowLow
Full trace visualizationInteractive HTML report with call graphLowMedium