diff --git a/crates/ty_python_semantic/resources/mdtest/attributes.md b/crates/ty_python_semantic/resources/mdtest/attributes.md index 1e4305d5ba..e8d5132f60 100644 --- a/crates/ty_python_semantic/resources/mdtest/attributes.md +++ b/crates/ty_python_semantic/resources/mdtest/attributes.md @@ -2576,13 +2576,11 @@ reveal_type(Answer.__members__) # revealed: MappingProxyType[str, Unknown] ## Divergent inferred implicit instance attribute types ```py -# TODO: This test currently panics, see https://github.com/astral-sh/ty/issues/837 +class C: + def f(self, other: "C"): + self.x = (other.x, 1) -# class C: -# def f(self, other: "C"): -# self.x = (other.x, 1) -# -# reveal_type(C().x) # revealed: Unknown | tuple[Divergent, Literal[1]] +reveal_type(C().x) # revealed: Unknown | tuple[Divergent, Literal[1]] ``` ## Attributes of standard library modules that aren't yet defined diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 583926254e..8676ebdab5 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -827,7 +827,7 @@ impl<'db> Type<'db> { Self::Dynamic(DynamicType::Unknown) } - pub(crate) fn divergent(scope: ScopeId<'db>) -> Self { + pub(crate) fn divergent(scope: Option>) -> Self { Self::Dynamic(DynamicType::Divergent(DivergentType { scope })) } @@ -7646,7 +7646,7 @@ impl<'db> KnownInstanceType<'db> { #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, salsa::Update, get_size2::GetSize)] pub struct DivergentType<'db> { /// The scope where this divergence was detected. - scope: ScopeId<'db>, + scope: Option>, } #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, salsa::Update, get_size2::GetSize)] @@ -11751,7 +11751,7 @@ pub(crate) mod tests { let file_scope_id = FileScopeId::global(); let scope = file_scope_id.to_scope_id(&db, file); - let div = Type::Dynamic(DynamicType::Divergent(DivergentType { scope })); + let div = Type::Dynamic(DynamicType::Divergent(DivergentType { scope: Some(scope) })); // The `Divergent` type must not be eliminated in union with other dynamic types, // as this would prevent detection of divergent type inference using `Divergent`. diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index 6221202308..74d1aef189 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -30,7 +30,9 @@ use crate::types::member::{Member, class_member}; use crate::types::signatures::{CallableSignature, Parameter, Parameters, Signature}; use crate::types::tuple::{TupleSpec, TupleType}; use crate::types::typed_dict::typed_dict_params_from_class_def; -use crate::types::visitor::{NonAtomicType, TypeKind, TypeVisitor, walk_non_atomic_type}; +use crate::types::visitor::{ + MAX_SPECIALIZATION_DEPTH, NonAtomicType, TypeKind, TypeVisitor, walk_non_atomic_type, +}; use crate::types::{ ApplyTypeMappingVisitor, Binding, BoundSuperType, CallableType, DataclassFlags, DataclassParams, DeprecatedInstance, FindLegacyTypeVarsVisitor, HasRelationToVisitor, @@ -1609,19 +1611,6 @@ impl<'db> ClassLiteral<'db> { db: &'db dyn Db, f: impl FnOnce(GenericContext<'db>) -> Specialization<'db>, ) -> ClassType<'db> { - // To prevent infinite recursion during type inference for infinite types, we fall back to - // `C[Divergent]` once a certain amount of levels of specialization have occurred. For - // example: - // - // ```py - // x = 1 - // while random_bool(): - // x = [x] - // - // reveal_type(x) # Unknown | Literal[1] | list[Divergent] - // ``` - const MAX_SPECIALIZATION_DEPTH: usize = 10; - match self.generic_context(db) { None => ClassType::NonGeneric(self), Some(generic_context) => { @@ -1629,11 +1618,8 @@ impl<'db> ClassLiteral<'db> { for (idx, ty) in specialization.types(db).iter().enumerate() { if specialization_depth(db, *ty) > MAX_SPECIALIZATION_DEPTH { - specialization = specialization.with_replaced_type( - db, - idx, - Type::divergent(self.body_scope(db)), - ); + specialization = + specialization.with_replaced_type(db, idx, Type::divergent(None)); } } diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index f2c256f304..fdafb2f918 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -62,7 +62,7 @@ mod builder; mod tests; /// How many fixpoint iterations to allow before falling back to Divergent type. -const ITERATIONS_BEFORE_FALLBACK: u32 = 10; +const ITERATIONS_BEFORE_FALLBACK: u32 = 20; /// Infer all types for a [`ScopeId`], including all definitions and expressions in that scope. /// Use when checking a scope, or needing to provide a type for an arbitrary expression in the @@ -567,7 +567,7 @@ impl<'db> CycleRecovery<'db> { fn fallback_type(self) -> Type<'db> { match self { Self::Initial => Type::Never, - Self::Divergent(scope) => Type::divergent(scope), + Self::Divergent(scope) => Type::divergent(Some(scope)), } } } diff --git a/crates/ty_python_semantic/src/types/infer/builder.rs b/crates/ty_python_semantic/src/types/infer/builder.rs index 9c6826e71e..419b85fda6 100644 --- a/crates/ty_python_semantic/src/types/infer/builder.rs +++ b/crates/ty_python_semantic/src/types/infer/builder.rs @@ -5968,7 +5968,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { let mut annotated_elt_tys = annotated_tuple.as_ref().map(Tuple::all_elements); let db = self.db(); - let divergent = Type::divergent(self.scope()); + let divergent = Type::divergent(Some(self.scope())); let element_types = elts.iter().map(|element| { let annotated_elt_ty = annotated_elt_tys.as_mut().and_then(Iterator::next).copied(); let element_type = self.infer_expression(element, TypeContext::new(annotated_elt_ty)); diff --git a/crates/ty_python_semantic/src/types/infer/builder/type_expression.rs b/crates/ty_python_semantic/src/types/infer/builder/type_expression.rs index 69c6b8a165..4bed7ce7a3 100644 --- a/crates/ty_python_semantic/src/types/infer/builder/type_expression.rs +++ b/crates/ty_python_semantic/src/types/infer/builder/type_expression.rs @@ -22,7 +22,7 @@ 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 mut ty = self.infer_type_expression_no_store(expression); - let divergent = Type::divergent(self.scope()); + let divergent = Type::divergent(Some(self.scope())); if ty.has_divergent_type(self.db(), divergent) { ty = divergent; } diff --git a/crates/ty_python_semantic/src/types/instance.rs b/crates/ty_python_semantic/src/types/instance.rs index ac081ca02a..76dc447fd0 100644 --- a/crates/ty_python_semantic/src/types/instance.rs +++ b/crates/ty_python_semantic/src/types/instance.rs @@ -12,10 +12,11 @@ use crate::types::enums::is_single_member_enum; use crate::types::generics::{InferableTypeVars, walk_specialization}; use crate::types::protocol_class::walk_protocol_interface; use crate::types::tuple::{TupleSpec, TupleType}; +use crate::types::visitor::MAX_SPECIALIZATION_DEPTH; use crate::types::{ ApplyTypeMappingVisitor, ClassBase, ClassLiteral, FindLegacyTypeVarsVisitor, HasRelationToVisitor, IsDisjointVisitor, IsEquivalentVisitor, NormalizedVisitor, TypeContext, - TypeMapping, TypeRelation, VarianceInferable, + TypeMapping, TypeRelation, VarianceInferable, specialization_depth, }; use crate::{Db, FxOrderSet}; @@ -72,7 +73,13 @@ impl<'db> Type<'db> { { Type::tuple(TupleType::heterogeneous( db, - elements.into_iter().map(Into::into), + elements.into_iter().map(Into::into).map(|ty| { + if specialization_depth(db, ty) > MAX_SPECIALIZATION_DEPTH { + Type::divergent(None) + } else { + ty + } + }), )) } diff --git a/crates/ty_python_semantic/src/types/visitor.rs b/crates/ty_python_semantic/src/types/visitor.rs index d49ba3ac15..30d36acb5f 100644 --- a/crates/ty_python_semantic/src/types/visitor.rs +++ b/crates/ty_python_semantic/src/types/visitor.rs @@ -301,6 +301,19 @@ pub(super) fn any_over_type<'db>( visitor.found_matching_type.get() } +// To prevent infinite recursion during type inference for infinite types, we fall back to +// `C[Divergent]` once a certain amount of levels of specialization have occurred. For +// example: +// +// ```py +// x = 1 +// while random_bool(): +// x = [x] +// +// reveal_type(x) # Unknown | Literal[1] | list[Divergent] +// ``` +pub(super) const MAX_SPECIALIZATION_DEPTH: usize = 10; + /// Returns the maximum number of layers of generic specializations for a given type. /// /// For example, `int` has a depth of `0`, `list[int]` has a depth of `1`, and `list[set[int]]`