[ty] Add cycle handling for unpacking targets (#18078)
Some checks are pending
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo clippy (push) Blocked by required conditions
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / cargo test (windows) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (release) (push) Waiting to run
CI / cargo build (msrv) (push) Blocked by required conditions
CI / cargo fuzz build (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / ecosystem (push) Blocked by required conditions
CI / Fuzz for new ty panics (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / pre-commit (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / test ruff-lsp (push) Blocked by required conditions
CI / check playground (push) Blocked by required conditions
CI / benchmarks (push) Blocked by required conditions
[ty Playground] Release / publish (push) Waiting to run

## Summary

This PR adds cycle handling for `infer_unpack_types` based on the
analysis in astral-sh/ty#364.

Fixes: astral-sh/ty#364

## Test Plan

Add a cycle handling test for unpacking in `cycle.md`
This commit is contained in:
Dhruv Manilawala 2025-05-14 02:57:48 +05:30 committed by GitHub
parent 65e48cb439
commit 8cbd433a31
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 60 additions and 3 deletions

View file

@ -307,7 +307,7 @@ fn single_expression_cycle_initial<'db>(
/// involved in an unpacking operation. It returns a result-like object that can be used to get the
/// type of the variables involved in this unpacking along with any violations that are detected
/// during this unpacking.
#[salsa::tracked(returns(ref))]
#[salsa::tracked(returns(ref), cycle_fn=unpack_cycle_recover, cycle_initial=unpack_cycle_initial)]
pub(super) fn infer_unpack_types<'db>(db: &'db dyn Db, unpack: Unpack<'db>) -> UnpackResult<'db> {
let file = unpack.file(db);
let _span =
@ -318,6 +318,19 @@ pub(super) fn infer_unpack_types<'db>(db: &'db dyn Db, unpack: Unpack<'db>) -> U
unpacker.finish()
}
fn unpack_cycle_recover<'db>(
_db: &'db dyn Db,
_value: &UnpackResult<'db>,
_count: u32,
_unpack: Unpack<'db>,
) -> salsa::CycleRecoveryAction<UnpackResult<'db>> {
salsa::CycleRecoveryAction::Iterate
}
fn unpack_cycle_initial<'db>(_db: &'db dyn Db, _unpack: Unpack<'db>) -> UnpackResult<'db> {
UnpackResult::cycle_fallback(Type::Never)
}
/// Returns the type of the nearest enclosing class for the given scope.
///
/// This function walks up the ancestor scopes starting from the given scope,

View file

@ -287,6 +287,7 @@ impl<'db> Unpacker<'db> {
UnpackResult {
diagnostics: self.context.finish(),
targets: self.targets,
cycle_fallback_type: None,
}
}
}
@ -295,21 +296,46 @@ impl<'db> Unpacker<'db> {
pub(crate) struct UnpackResult<'db> {
targets: FxHashMap<ScopedExpressionId, Type<'db>>,
diagnostics: TypeCheckDiagnostics,
/// The fallback type for missing expressions.
///
/// This is used only when constructing a cycle-recovery `UnpackResult`.
cycle_fallback_type: Option<Type<'db>>,
}
impl<'db> UnpackResult<'db> {
/// Returns the inferred type for a given sub-expression of the left-hand side target
/// of an unpacking assignment.
///
/// Panics if a scoped expression ID is passed in that does not correspond to a sub-
/// # Panics
///
/// May panic if a scoped expression ID is passed in that does not correspond to a sub-
/// expression of the target.
#[track_caller]
pub(crate) fn expression_type(&self, expr_id: ScopedExpressionId) -> Type<'db> {
self.targets[&expr_id]
self.try_expression_type(expr_id).expect(
"expression should belong to this `UnpackResult` and \
`Unpacker` should have inferred a type for it",
)
}
pub(crate) fn try_expression_type(&self, expr_id: ScopedExpressionId) -> Option<Type<'db>> {
self.targets
.get(&expr_id)
.copied()
.or(self.cycle_fallback_type)
}
/// Returns the diagnostics in this unpacking assignment.
pub(crate) fn diagnostics(&self) -> &TypeCheckDiagnostics {
&self.diagnostics
}
pub(crate) fn cycle_fallback(cycle_fallback_type: Type<'db>) -> Self {
Self {
targets: FxHashMap::default(),
diagnostics: TypeCheckDiagnostics::default(),
cycle_fallback_type: Some(cycle_fallback_type),
}
}
}