Skip to content

Commit c18fe17

Browse files
committed
[ty] no more diverging query cycles in type expressions
1 parent 1cd8ab3 commit c18fe17

File tree

11 files changed

+331
-44
lines changed

11 files changed

+331
-44
lines changed
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Implicit type aliases
2+
3+
Implicit type aliases are the earliest form of type alias, introduced in PEP 484. They have no
4+
special marker, just an ordinary assignment statement.
5+
6+
## Basic
7+
8+
We support simple type aliases with no extra effort, when the "value type" of the RHS is still a
9+
valid type for use in a type expression:
10+
11+
```py
12+
MyInt = int
13+
14+
def f(x: MyInt):
15+
reveal_type(x) # revealed: int
16+
17+
f(1)
18+
```
19+
20+
## Recursive
21+
22+
### Old union syntax
23+
24+
```py
25+
from typing import Union
26+
27+
T = list[Union["T", None]]
28+
```
29+
30+
### New union syntax
31+
32+
```toml
33+
[environment]
34+
python-version = "3.12"
35+
```
36+
37+
```py
38+
T = list["T" | None]
39+
```

crates/ty_python_semantic/src/types.rs

Lines changed: 121 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,6 @@ use crate::types::signatures::{Parameter, ParameterForm, Parameters, walk_signat
6565
use crate::types::tuple::TupleSpec;
6666
pub(crate) use crate::types::typed_dict::{TypedDictParams, TypedDictType, walk_typed_dict_type};
6767
use crate::types::variance::{TypeVarVariance, VarianceInferable};
68-
use crate::types::visitor::any_over_type;
6968
use crate::unpack::EvaluationMode;
7069
pub use crate::util::diagnostics::add_inferred_python_version_hint_to_diagnostic;
7170
use crate::{Db, FxOrderSet, Module, Program};
@@ -222,6 +221,10 @@ pub(crate) struct TryBool;
222221
pub(crate) type NormalizedVisitor<'db> = TypeTransformer<'db, Normalized>;
223222
pub(crate) struct Normalized;
224223

224+
/// A [`CycleDetector`] that is used in `has_divergent_type` methods.
225+
pub(crate) type HasDivergentTypeVisitor<'db> = CycleDetector<HasDivergentType, Type<'db>, bool>;
226+
pub(crate) struct HasDivergentType;
227+
225228
/// How a generic type has been specialized.
226229
///
227230
/// This matters only if there is at least one invariant type parameter.
@@ -588,6 +591,19 @@ impl<'db> PropertyInstanceType<'db> {
588591

589592
getter_equivalence.and(db, setter_equivalence)
590593
}
594+
595+
fn has_divergent_type_impl(
596+
self,
597+
db: &'db dyn Db,
598+
div: Type<'db>,
599+
visitor: &HasDivergentTypeVisitor<'db>,
600+
) -> bool {
601+
self.getter(db)
602+
.is_some_and(|ty| ty.has_divergent_type_impl(db, div, visitor))
603+
|| self
604+
.setter(db)
605+
.is_some_and(|ty| ty.has_divergent_type_impl(db, div, visitor))
606+
}
591607
}
592608

593609
bitflags! {
@@ -6506,10 +6522,74 @@ impl<'db> Type<'db> {
65066522
}
65076523

65086524
pub(super) fn has_divergent_type(self, db: &'db dyn Db, div: Type<'db>) -> bool {
6509-
any_over_type(db, self, &|ty| match ty {
6510-
Type::Dynamic(DynamicType::Divergent(_)) => ty == div,
6511-
_ => false,
6512-
})
6525+
self.has_divergent_type_impl(db, div, &HasDivergentTypeVisitor::new(false))
6526+
}
6527+
6528+
pub(super) fn has_divergent_type_impl(
6529+
self,
6530+
db: &'db dyn Db,
6531+
div: Type<'db>,
6532+
visitor: &HasDivergentTypeVisitor<'db>,
6533+
) -> bool {
6534+
// We don't use `any_over_type` here because we don't need/want to descend into lazy parts
6535+
// of types (typevar bounds/constraints, type alias values, etc) here.
6536+
match self {
6537+
Type::Dynamic(DynamicType::Divergent(_)) => self == div,
6538+
Type::Union(union) => union.has_divergent_type_impl(db, div, visitor),
6539+
Type::Intersection(intersection) => {
6540+
intersection.has_divergent_type_impl(db, div, visitor)
6541+
}
6542+
Type::GenericAlias(alias) => visitor.visit(self, || {
6543+
alias
6544+
.specialization(db)
6545+
.has_divergent_type_impl(db, div, visitor)
6546+
}),
6547+
Type::NominalInstance(instance) => visitor.visit(self, || {
6548+
instance.class(db).has_divergent_type_impl(db, div, visitor)
6549+
}),
6550+
Type::Callable(callable) => {
6551+
visitor.visit(self, || callable.has_divergent_type_impl(db, div, visitor))
6552+
}
6553+
Type::ProtocolInstance(protocol) => {
6554+
visitor.visit(self, || protocol.has_divergent_type_impl(db, div, visitor))
6555+
}
6556+
Type::PropertyInstance(property) => property.has_divergent_type_impl(db, div, visitor),
6557+
Type::TypeIs(type_is) => type_is
6558+
.return_type(db)
6559+
.has_divergent_type_impl(db, div, visitor),
6560+
Type::SubclassOf(subclass_of) => visitor.visit(self, || {
6561+
subclass_of.has_divergent_type_impl(db, div, visitor)
6562+
}),
6563+
Type::TypedDict(typed_dict) => visitor.visit(self, || {
6564+
typed_dict
6565+
.defining_class()
6566+
.has_divergent_type_impl(db, div, visitor)
6567+
}),
6568+
Type::Never
6569+
| Type::AlwaysTruthy
6570+
| Type::AlwaysFalsy
6571+
| Type::WrapperDescriptor(_)
6572+
| Type::DataclassDecorator(_)
6573+
| Type::DataclassTransformer(_)
6574+
| Type::ModuleLiteral(_)
6575+
| Type::ClassLiteral(_)
6576+
| Type::IntLiteral(_)
6577+
| Type::BooleanLiteral(_)
6578+
| Type::LiteralString
6579+
| Type::StringLiteral(_)
6580+
| Type::BytesLiteral(_)
6581+
| Type::EnumLiteral(_)
6582+
| Type::BoundSuper(_)
6583+
| Type::SpecialForm(_)
6584+
| Type::KnownInstance(_)
6585+
| Type::NonInferableTypeVar(_)
6586+
| Type::TypeVar(_)
6587+
| Type::FunctionLiteral(_)
6588+
| Type::KnownBoundMethod(_)
6589+
| Type::BoundMethod(_)
6590+
| Type::Dynamic(_)
6591+
| Type::TypeAlias(_) => false,
6592+
}
65136593
}
65146594
}
65156595

@@ -9155,6 +9235,16 @@ impl<'db> CallableType<'db> {
91559235
.is_equivalent_to_impl(db, other.signatures(db), visitor)
91569236
})
91579237
}
9238+
9239+
fn has_divergent_type_impl(
9240+
self,
9241+
db: &'db dyn Db,
9242+
div: Type<'db>,
9243+
visitor: &HasDivergentTypeVisitor<'db>,
9244+
) -> bool {
9245+
self.signatures(db)
9246+
.has_divergent_type_impl(db, div, visitor)
9247+
}
91589248
}
91599249

91609250
/// Represents a specific instance of a bound method type for a builtin class.
@@ -10129,6 +10219,17 @@ impl<'db> UnionType<'db> {
1012910219

1013010220
ConstraintSet::from(sorted_self == other.normalized(db))
1013110221
}
10222+
10223+
fn has_divergent_type_impl(
10224+
self,
10225+
db: &'db dyn Db,
10226+
div: Type<'db>,
10227+
visitor: &HasDivergentTypeVisitor<'db>,
10228+
) -> bool {
10229+
self.elements(db)
10230+
.iter()
10231+
.any(|ty| ty.has_divergent_type_impl(db, div, visitor))
10232+
}
1013210233
}
1013310234

1013410235
#[salsa::interned(debug, heap_size=IntersectionType::heap_size)]
@@ -10344,6 +10445,21 @@ impl<'db> IntersectionType<'db> {
1034410445
ruff_memory_usage::order_set_heap_size(positive)
1034510446
+ ruff_memory_usage::order_set_heap_size(negative)
1034610447
}
10448+
10449+
fn has_divergent_type_impl(
10450+
self,
10451+
db: &'db dyn Db,
10452+
div: Type<'db>,
10453+
visitor: &HasDivergentTypeVisitor<'db>,
10454+
) -> bool {
10455+
self.positive(db)
10456+
.iter()
10457+
.any(|ty| ty.has_divergent_type_impl(db, div, visitor))
10458+
|| self
10459+
.negative(db)
10460+
.iter()
10461+
.any(|ty| ty.has_divergent_type_impl(db, div, visitor))
10462+
}
1034710463
}
1034810464

1034910465
/// # Ordering

crates/ty_python_semantic/src/types/class.rs

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,12 @@ use crate::types::tuple::{TupleSpec, TupleType};
2626
use crate::types::typed_dict::typed_dict_params_from_class_def;
2727
use crate::types::{
2828
ApplyTypeMappingVisitor, Binding, BoundSuperError, BoundSuperType, CallableType,
29-
DataclassParams, DeprecatedInstance, FindLegacyTypeVarsVisitor, HasRelationToVisitor,
30-
IsEquivalentVisitor, KnownInstanceType, ManualPEP695TypeAliasType, MaterializationKind,
31-
NormalizedVisitor, PropertyInstanceType, StringLiteralType, TypeAliasType, TypeContext,
32-
TypeMapping, TypeRelation, TypeVarBoundOrConstraints, TypeVarInstance, TypeVarKind,
33-
TypedDictParams, UnionBuilder, VarianceInferable, declaration_type, infer_definition_types,
29+
DataclassParams, DeprecatedInstance, FindLegacyTypeVarsVisitor, HasDivergentTypeVisitor,
30+
HasRelationToVisitor, IsEquivalentVisitor, KnownInstanceType, ManualPEP695TypeAliasType,
31+
MaterializationKind, NormalizedVisitor, PropertyInstanceType, StringLiteralType, TypeAliasType,
32+
TypeContext, TypeMapping, TypeRelation, TypeVarBoundOrConstraints, TypeVarInstance,
33+
TypeVarKind, TypedDictParams, UnionBuilder, VarianceInferable, declaration_type,
34+
infer_definition_types,
3435
};
3536
use crate::{
3637
Db, FxIndexMap, FxOrderSet, Program,
@@ -614,6 +615,20 @@ impl<'db> ClassType<'db> {
614615
}
615616
}
616617

618+
pub(super) fn has_divergent_type_impl(
619+
self,
620+
db: &'db dyn Db,
621+
div: Type<'db>,
622+
visitor: &HasDivergentTypeVisitor<'db>,
623+
) -> bool {
624+
match self {
625+
ClassType::NonGeneric(_) => false,
626+
ClassType::Generic(generic) => generic
627+
.specialization(db)
628+
.has_divergent_type_impl(db, div, visitor),
629+
}
630+
}
631+
617632
/// Return the metaclass of this class, or `type[Unknown]` if the metaclass cannot be inferred.
618633
pub(super) fn metaclass(self, db: &'db dyn Db) -> Type<'db> {
619634
let (class_literal, specialization) = self.class_literal(db);

crates/ty_python_semantic/src/types/generics.rs

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,11 @@ use crate::types::instance::{Protocol, ProtocolInstanceType};
1616
use crate::types::signatures::{Parameter, Parameters, Signature};
1717
use crate::types::tuple::{TupleSpec, TupleType, walk_tuple_type};
1818
use crate::types::{
19-
ApplyTypeMappingVisitor, BoundTypeVarInstance, FindLegacyTypeVarsVisitor, HasRelationToVisitor,
20-
IsEquivalentVisitor, KnownClass, KnownInstanceType, MaterializationKind, NormalizedVisitor,
21-
Type, TypeMapping, TypeRelation, TypeVarBoundOrConstraints, TypeVarInstance, TypeVarVariance,
22-
UnionType, binding_type, declaration_type,
19+
ApplyTypeMappingVisitor, BoundTypeVarInstance, FindLegacyTypeVarsVisitor,
20+
HasDivergentTypeVisitor, HasRelationToVisitor, IsEquivalentVisitor, KnownClass,
21+
KnownInstanceType, MaterializationKind, NormalizedVisitor, Type, TypeMapping, TypeRelation,
22+
TypeVarBoundOrConstraints, TypeVarInstance, TypeVarVariance, UnionType, binding_type,
23+
declaration_type,
2324
};
2425
use crate::{Db, FxOrderSet};
2526

@@ -926,6 +927,20 @@ impl<'db> Specialization<'db> {
926927
// A tuple's specialization will include all of its element types, so we don't need to also
927928
// look in `self.tuple`.
928929
}
930+
931+
pub(super) fn has_divergent_type_impl(
932+
self,
933+
db: &'db dyn Db,
934+
div: Type<'db>,
935+
visitor: &HasDivergentTypeVisitor<'db>,
936+
) -> bool {
937+
self.types(db)
938+
.iter()
939+
.any(|ty| ty.has_divergent_type_impl(db, div, visitor))
940+
|| self
941+
.tuple_inner(db)
942+
.is_some_and(|tuple| tuple.has_divergent_type_impl(db, div, visitor))
943+
}
929944
}
930945

931946
/// A mapping between type variables and types.

crates/ty_python_semantic/src/types/infer.rs

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -111,12 +111,18 @@ pub(crate) fn infer_definition_types<'db>(
111111
}
112112

113113
fn definition_cycle_recover<'db>(
114-
_db: &'db dyn Db,
114+
db: &'db dyn Db,
115115
_value: &DefinitionInference<'db>,
116-
_count: u32,
117-
_definition: Definition<'db>,
116+
count: u32,
117+
definition: Definition<'db>,
118118
) -> salsa::CycleRecoveryAction<DefinitionInference<'db>> {
119-
salsa::CycleRecoveryAction::Iterate
119+
if count == ITERATIONS_BEFORE_FALLBACK {
120+
salsa::CycleRecoveryAction::Fallback(DefinitionInference::cycle_fallback(
121+
definition.scope(db),
122+
))
123+
} else {
124+
salsa::CycleRecoveryAction::Iterate
125+
}
120126
}
121127

122128
fn definition_cycle_initial<'db>(
@@ -151,6 +157,9 @@ pub(crate) fn infer_deferred_types<'db>(
151157
.finish_definition()
152158
}
153159

160+
/// How many fixpoint iterations to allow before falling back to Divergent type.
161+
const ITERATIONS_BEFORE_FALLBACK: u32 = 10;
162+
154163
fn deferred_cycle_recover<'db>(
155164
_db: &'db dyn Db,
156165
_value: &DefinitionInference<'db>,
@@ -207,9 +216,6 @@ fn infer_expression_types_impl<'db>(
207216
.finish_expression()
208217
}
209218

210-
/// How many fixpoint iterations to allow before falling back to Divergent type.
211-
const ITERATIONS_BEFORE_FALLBACK: u32 = 10;
212-
213219
fn expression_cycle_recover<'db>(
214220
db: &'db dyn Db,
215221
_value: &ExpressionInference<'db>,
@@ -623,6 +629,22 @@ impl<'db> DefinitionInference<'db> {
623629
}
624630
}
625631

632+
fn cycle_fallback(scope: ScopeId<'db>) -> Self {
633+
let _ = scope;
634+
635+
Self {
636+
expressions: FxHashMap::default(),
637+
bindings: Box::default(),
638+
declarations: Box::default(),
639+
#[cfg(debug_assertions)]
640+
scope,
641+
extra: Some(Box::new(DefinitionInferenceExtra {
642+
cycle_recovery: Some(CycleRecovery::Divergent(scope)),
643+
..DefinitionInferenceExtra::default()
644+
})),
645+
}
646+
}
647+
626648
pub(crate) fn expression_type(&self, expression: impl Into<ExpressionNodeKey>) -> Type<'db> {
627649
self.try_expression_type(expression)
628650
.unwrap_or_else(Type::unknown)

0 commit comments

Comments
 (0)