diff --git a/crates/ty_python_semantic/src/semantic_index/definition.rs b/crates/ty_python_semantic/src/semantic_index/definition.rs index 9e1e6a89cf..869944eba5 100644 --- a/crates/ty_python_semantic/src/semantic_index/definition.rs +++ b/crates/ty_python_semantic/src/semantic_index/definition.rs @@ -109,10 +109,6 @@ impl<'db> DefinitionState<'db> { || matches!(self, DefinitionState::Defined(def) if f(def)) } - pub(crate) fn is_undefined(self) -> bool { - matches!(self, DefinitionState::Undefined) - } - #[allow(unused)] pub(crate) fn definition(self) -> Option> { match self { diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index 78532e12de..165a0a3b85 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -1640,19 +1640,21 @@ impl<'db> ClassLiteral<'db> { continue; } - let mut attribute_assignments = attribute_assignments.peekable(); - let unbound_visibility = attribute_assignments - .peek() - .map(|attribute_assignment| { - if attribute_assignment.binding.is_undefined() { - method_map.is_binding_visible(db, attribute_assignment) - } else { - Truthiness::AlwaysFalse - } - }) - .unwrap_or(Truthiness::AlwaysFalse); + // Storage for the implicit `DefinitionState::Undefined` binding. If present, it + // will be the first binding in the `attribute_assignments` iterator. + let mut unbound_binding = None; for attribute_assignment in attribute_assignments { + if let DefinitionState::Undefined = attribute_assignment.binding { + // Store the implicit unbound binding here so that we can delay the + // computation of `unbound_visibility` to the point when we actually + // need it. This is an optimization for the common case where the + // `unbound` binding is the only binding of the `name` attribute, + // i.e. if there is no `self.name = …` assignment in this method. + unbound_binding = Some(attribute_assignment); + continue; + } + let DefinitionState::Defined(binding) = attribute_assignment.binding else { continue; }; @@ -1676,6 +1678,11 @@ impl<'db> ClassLiteral<'db> { // There is at least one attribute assignment that may be visible, // so if `unbound_visibility` is always false then this attribute is considered bound. // TODO: this is incomplete logic since the attributes bound after termination are considered visible. + let unbound_visibility = unbound_binding + .as_ref() + .map(|binding| method_map.is_binding_visible(db, binding)) + .unwrap_or(Truthiness::AlwaysFalse); + if unbound_visibility .negate() .and(is_method_visible)