From 00f3c7d1d568d672facfd8155f50a42e3e09981e Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Fri, 29 Dec 2023 11:13:24 -0400 Subject: [PATCH] Respect attribute chains when resolving builtin call paths (#9309) ## Summary When resolving `dict.__dict__`, we were discarding the `.__dict__` segment when computing the call path. ## Test Plan `cargo test` --- .../resources/test/fixtures/pyflakes/F821_25.py | 5 +++++ crates/ruff_linter/src/rules/pyflakes/mod.rs | 1 + ...__rules__pyflakes__tests__F821_F821_25.py.snap | 4 ++++ crates/ruff_python_semantic/src/analyze/typing.rs | 4 ++-- crates/ruff_python_semantic/src/model.rs | 15 ++++++++++++++- 5 files changed, 26 insertions(+), 3 deletions(-) create mode 100644 crates/ruff_linter/resources/test/fixtures/pyflakes/F821_25.py create mode 100644 crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F821_F821_25.py.snap diff --git a/crates/ruff_linter/resources/test/fixtures/pyflakes/F821_25.py b/crates/ruff_linter/resources/test/fixtures/pyflakes/F821_25.py new file mode 100644 index 0000000000000..ad5f5e9bb3f99 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/pyflakes/F821_25.py @@ -0,0 +1,5 @@ +"""Test for attribute accesses on builtins.""" + +a = type({}).__dict__['fromkeys'] +b = dict.__dict__['fromkeys'] +assert a is b diff --git a/crates/ruff_linter/src/rules/pyflakes/mod.rs b/crates/ruff_linter/src/rules/pyflakes/mod.rs index 84b54f9126a08..3a19dba6ca7cb 100644 --- a/crates/ruff_linter/src/rules/pyflakes/mod.rs +++ b/crates/ruff_linter/src/rules/pyflakes/mod.rs @@ -144,6 +144,7 @@ mod tests { #[test_case(Rule::UndefinedName, Path::new("F821_22.ipynb"))] #[test_case(Rule::UndefinedName, Path::new("F821_23.py"))] #[test_case(Rule::UndefinedName, Path::new("F821_24.py"))] + #[test_case(Rule::UndefinedName, Path::new("F821_25.py"))] #[test_case(Rule::UndefinedExport, Path::new("F822_0.py"))] #[test_case(Rule::UndefinedExport, Path::new("F822_1.py"))] #[test_case(Rule::UndefinedExport, Path::new("F822_2.py"))] diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F821_F821_25.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F821_F821_25.py.snap new file mode 100644 index 0000000000000..d0b409f39ee0b --- /dev/null +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F821_F821_25.py.snap @@ -0,0 +1,4 @@ +--- +source: crates/ruff_linter/src/rules/pyflakes/mod.rs +--- + diff --git a/crates/ruff_python_semantic/src/analyze/typing.rs b/crates/ruff_python_semantic/src/analyze/typing.rs index 1b45992420986..f63a4f5215f28 100644 --- a/crates/ruff_python_semantic/src/analyze/typing.rs +++ b/crates/ruff_python_semantic/src/analyze/typing.rs @@ -15,7 +15,7 @@ use crate::analyze::type_inference::{PythonType, ResolvedPythonType}; use crate::model::SemanticModel; use crate::{Binding, BindingKind}; -#[derive(Copy, Clone)] +#[derive(Debug, Copy, Clone)] pub enum Callable { Bool, Cast, @@ -26,7 +26,7 @@ pub enum Callable { MypyExtension, } -#[derive(Copy, Clone)] +#[derive(Debug, Copy, Clone)] pub enum SubscriptKind { /// A subscript of the form `typing.Literal["foo", "bar"]`, i.e., a literal. Literal, diff --git a/crates/ruff_python_semantic/src/model.rs b/crates/ruff_python_semantic/src/model.rs index 65dd5e6ae0bcd..1f9d3715ef1ca 100644 --- a/crates/ruff_python_semantic/src/model.rs +++ b/crates/ruff_python_semantic/src/model.rs @@ -707,7 +707,20 @@ impl<'a> SemanticModel<'a> { }; Some(resolved) } - BindingKind::Builtin => Some(smallvec!["", head.id.as_str()]), + BindingKind::Builtin => { + if value.is_name_expr() { + // Ex) `dict` + Some(smallvec!["", head.id.as_str()]) + } else { + // Ex) `dict.__dict__` + let value_path = collect_call_path(value)?; + Some( + std::iter::once("") + .chain(value_path.iter().copied()) + .collect(), + ) + } + } BindingKind::ClassDefinition(_) | BindingKind::FunctionDefinition(_) => { let value_path = collect_call_path(value)?; let resolved: CallPath = self