[ty] Further improve details around which expressions should be deferred in stub files (#21456)

## Summary

- Always restore the previous `deferred_state` after parsing a type
expression: we don't want that state leaking out into other contexts
where we shouldn't be deferring expression inference
- Always defer the right-hand-side of a PEP-613 type alias in a stub
file, allowing for forward references on the right-hand side of `T:
TypeAlias = X | Y` in a stub file

Addresses @carljm's review in
https://github.com/astral-sh/ruff/pull/21401#discussion_r2524260153

## Test Plan

I added a regression test for a regression that the first version of
this PR introduced (we need to make sure the r.h.s. of a PEP-613
`TypeAlias`es is always deferred in a stub file)
This commit is contained in:
Alex Waygood
2025-11-14 21:07:02 +00:00
committed by GitHub
parent 2a2b719f00
commit 3e7e91724c
3 changed files with 58 additions and 7 deletions

View File

@@ -5486,10 +5486,23 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
self.dataclass_field_specifiers = specifiers;
}
let inferred_ty = self.infer_maybe_standalone_expression(
value,
TypeContext::new(Some(declared.inner_type())),
);
// We defer the r.h.s. of PEP-613 `TypeAlias` assignments in stub files.
let declared_type = declared.inner_type();
let previous_deferred_state = self.deferred_state;
if matches!(
declared_type,
Type::SpecialForm(SpecialFormType::TypeAlias)
| Type::Dynamic(DynamicType::TodoTypeAlias)
) && self.in_stub()
{
self.deferred_state = DeferredExpressionState::Deferred;
}
let inferred_ty = self
.infer_maybe_standalone_expression(value, TypeContext::new(Some(declared_type)));
self.deferred_state = previous_deferred_state;
self.dataclass_field_specifiers.clear();

View File

@@ -20,10 +20,12 @@ use crate::types::{
impl<'db> TypeInferenceBuilder<'db, '_> {
/// Infer the type of a type expression.
pub(super) fn infer_type_expression(&mut self, expression: &ast::Expr) -> Type<'db> {
let previous_deferred_state = self.deferred_state;
// `DeferredExpressionState::InStringAnnotation` takes precedence over other states.
// However, if it's not a stringified annotation, we must still ensure that annotation expressions
// are always deferred in stub files.
match self.deferred_state {
match previous_deferred_state {
DeferredExpressionState::None => {
if self.in_stub() {
self.deferred_state = DeferredExpressionState::Deferred;
@@ -31,8 +33,9 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
}
DeferredExpressionState::InStringAnnotation(_) | DeferredExpressionState::Deferred => {}
}
let mut ty = self.infer_type_expression_no_store(expression);
self.deferred_state = previous_deferred_state;
let divergent = Type::divergent(Some(self.scope()));
if ty.has_divergent_type(self.db(), divergent) {
ty = divergent;