Skip to content

Commit aca2a4e

Browse files
committed
[ty] no more diverging query cycles in type expressions
1 parent 82796df commit aca2a4e

File tree

11 files changed

+329
-42
lines changed

11 files changed

+329
-42
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! {
@@ -6516,10 +6532,74 @@ impl<'db> Type<'db> {
65166532
}
65176533

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

@@ -9181,6 +9261,16 @@ impl<'db> CallableType<'db> {
91819261
.is_equivalent_to_impl(db, other.signatures(db), visitor)
91829262
})
91839263
}
9264+
9265+
fn has_divergent_type_impl(
9266+
self,
9267+
db: &'db dyn Db,
9268+
div: Type<'db>,
9269+
visitor: &HasDivergentTypeVisitor<'db>,
9270+
) -> bool {
9271+
self.signatures(db)
9272+
.has_divergent_type_impl(db, div, visitor)
9273+
}
91849274
}
91859275

91869276
/// Represents a specific instance of a bound method type for a builtin class.
@@ -10155,6 +10245,17 @@ impl<'db> UnionType<'db> {
1015510245

1015610246
ConstraintSet::from(sorted_self == other.normalized(db))
1015710247
}
10248+
10249+
fn has_divergent_type_impl(
10250+
self,
10251+
db: &'db dyn Db,
10252+
div: Type<'db>,
10253+
visitor: &HasDivergentTypeVisitor<'db>,
10254+
) -> bool {
10255+
self.elements(db)
10256+
.iter()
10257+
.any(|ty| ty.has_divergent_type_impl(db, div, visitor))
10258+
}
1015810259
}
1015910260

1016010261
#[salsa::interned(debug, heap_size=IntersectionType::heap_size)]
@@ -10370,6 +10471,21 @@ impl<'db> IntersectionType<'db> {
1037010471
ruff_memory_usage::order_set_heap_size(positive)
1037110472
+ ruff_memory_usage::order_set_heap_size(negative)
1037210473
}
10474+
10475+
fn has_divergent_type_impl(
10476+
self,
10477+
db: &'db dyn Db,
10478+
div: Type<'db>,
10479+
visitor: &HasDivergentTypeVisitor<'db>,
10480+
) -> bool {
10481+
self.positive(db)
10482+
.iter()
10483+
.any(|ty| ty.has_divergent_type_impl(db, div, visitor))
10484+
|| self
10485+
.negative(db)
10486+
.iter()
10487+
.any(|ty| ty.has_divergent_type_impl(db, div, visitor))
10488+
}
1037310489
}
1037410490

1037510491
/// # 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)