Skip to content

Commit 2b731d1

Browse files
authored
[ty] Fix panic when attempting to provide autocompletions for an instance of a class that assigns attributes to self[0] (#18707)
1 parent cff5adf commit 2b731d1

File tree

4 files changed

+71
-13
lines changed

4 files changed

+71
-13
lines changed

crates/ty_ide/src/completion.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1835,6 +1835,21 @@ f"{f.<CURSOR>
18351835
test.assert_completions_include("method");
18361836
}
18371837

1838+
#[test]
1839+
fn no_panic_for_attribute_table_that_contains_subscript() {
1840+
let test = cursor_test(
1841+
r#"
1842+
class Point:
1843+
def orthogonal_direction(self):
1844+
self[0].is_zero
1845+
1846+
def test_point(p2: Point):
1847+
p2.<CURSOR>
1848+
"#,
1849+
);
1850+
test.assert_completions_include("orthogonal_direction");
1851+
}
1852+
18381853
impl CursorTest {
18391854
fn completions(&self) -> String {
18401855
self.completions_if(|_| true)

crates/ty_python_semantic/src/semantic_index/builder.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1964,12 +1964,14 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> {
19641964
| ast::Expr::Attribute(ast::ExprAttribute { ctx, .. })
19651965
| ast::Expr::Subscript(ast::ExprSubscript { ctx, .. }) => {
19661966
if let Ok(mut place_expr) = PlaceExpr::try_from(expr) {
1967-
if self.is_method_of_class().is_some() {
1967+
if self.is_method_of_class().is_some()
1968+
&& place_expr.is_instance_attribute_candidate()
1969+
{
19681970
// We specifically mark attribute assignments to the first parameter of a method,
19691971
// i.e. typically `self` or `cls`.
19701972
let accessed_object_refers_to_first_parameter = self
19711973
.current_first_parameter_name
1972-
.is_some_and(|fst| place_expr.root_name().as_str() == fst);
1974+
.is_some_and(|fst| place_expr.root_name() == fst);
19731975

19741976
if accessed_object_refers_to_first_parameter && place_expr.is_member() {
19751977
place_expr.mark_instance_attribute();

crates/ty_python_semantic/src/semantic_index/place.rs

Lines changed: 50 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -191,16 +191,59 @@ impl PlaceExpr {
191191
&self.root_name
192192
}
193193

194+
/// If the place expression has the form `<NAME>.<MEMBER>`
195+
/// (meaning it *may* be an instance attribute),
196+
/// return `Some(<MEMBER>)`. Else, return `None`.
197+
///
198+
/// This method is internal to the semantic-index submodule.
199+
/// It *only* checks that the AST structure of the `Place` is
200+
/// correct. It does not check whether the `Place` actually occurred in
201+
/// a method context, or whether the `<NAME>` actually refers to the first
202+
/// parameter of the method (i.e. `self`). To answer those questions,
203+
/// use [`Self::as_instance_attribute`].
204+
pub(super) fn as_instance_attribute_candidate(&self) -> Option<&Name> {
205+
if self.sub_segments.len() == 1 {
206+
self.sub_segments[0].as_member()
207+
} else {
208+
None
209+
}
210+
}
211+
212+
/// Return `true` if the place expression has the form `<NAME>.<MEMBER>`,
213+
/// indicating that it *may* be an instance attribute if we are in a method context.
214+
///
215+
/// This method is internal to the semantic-index submodule.
216+
/// It *only* checks that the AST structure of the `Place` is
217+
/// correct. It does not check whether the `Place` actually occurred in
218+
/// a method context, or whether the `<NAME>` actually refers to the first
219+
/// parameter of the method (i.e. `self`). To answer those questions,
220+
/// use [`Self::is_instance_attribute`].
221+
pub(super) fn is_instance_attribute_candidate(&self) -> bool {
222+
self.as_instance_attribute_candidate().is_some()
223+
}
224+
194225
/// Does the place expression have the form `self.{name}` (`self` is the first parameter of the method)?
195226
pub(super) fn is_instance_attribute_named(&self, name: &str) -> bool {
196-
self.is_instance_attribute()
197-
&& self.sub_segments.len() == 1
198-
&& self.sub_segments[0].as_member().unwrap().as_str() == name
227+
self.as_instance_attribute().map(Name::as_str) == Some(name)
199228
}
200229

201230
/// Is the place an instance attribute?
202-
pub fn is_instance_attribute(&self) -> bool {
203-
self.flags.contains(PlaceFlags::IS_INSTANCE_ATTRIBUTE)
231+
pub(crate) fn is_instance_attribute(&self) -> bool {
232+
let is_instance_attribute = self.flags.contains(PlaceFlags::IS_INSTANCE_ATTRIBUTE);
233+
if is_instance_attribute {
234+
debug_assert!(self.is_instance_attribute_candidate());
235+
}
236+
is_instance_attribute
237+
}
238+
239+
/// Return `Some(<ATTRIBUTE>)` if the place expression is an instance attribute.
240+
pub(crate) fn as_instance_attribute(&self) -> Option<&Name> {
241+
if self.is_instance_attribute() {
242+
debug_assert!(self.as_instance_attribute_candidate().is_some());
243+
self.as_instance_attribute_candidate()
244+
} else {
245+
None
246+
}
204247
}
205248

206249
/// Is the place used in its containing scope?
@@ -570,9 +613,9 @@ impl PlaceTable {
570613
self.places().filter(|place_expr| place_expr.is_name())
571614
}
572615

573-
pub fn instance_attributes(&self) -> impl Iterator<Item = &PlaceExpr> {
616+
pub fn instance_attributes(&self) -> impl Iterator<Item = &Name> {
574617
self.places()
575-
.filter(|place_expr| place_expr.is_instance_attribute())
618+
.filter_map(|place_expr| place_expr.as_instance_attribute())
576619
}
577620

578621
/// Returns the place named `name`.

crates/ty_python_semantic/src/types/ide_support.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -185,10 +185,8 @@ impl AllMembers {
185185
let index = semantic_index(db, file);
186186
for function_scope_id in attribute_scopes(db, class_body_scope) {
187187
let place_table = index.place_table(function_scope_id);
188-
for instance_attribute in place_table.instance_attributes() {
189-
let name = instance_attribute.sub_segments()[0].as_member().unwrap();
190-
self.members.insert(name.clone());
191-
}
188+
self.members
189+
.extend(place_table.instance_attributes().cloned());
192190
}
193191
}
194192
}

0 commit comments

Comments
 (0)