Use dynamic builtins list based on Python version (#13172)

## Summary

Closes https://github.com/astral-sh/ruff/issues/13037.
This commit is contained in:
Charlie Marsh 2024-09-01 13:03:44 -04:00 committed by GitHub
parent 3abd5c08a5
commit c4aad4b161
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 460 additions and 333 deletions

View file

@ -3,3 +3,9 @@ import float
from some import other as int from some import other as int
from some import input, exec from some import input, exec
from directory import new as dir from directory import new as dir
# See: https://github.com/astral-sh/ruff/issues/13037
import sys
if sys.version_info < (3, 11):
from exceptiongroup import BaseExceptionGroup, ExceptionGroup

View file

@ -55,7 +55,7 @@ use ruff_python_semantic::{
ModuleKind, ModuleSource, NodeId, ScopeId, ScopeKind, SemanticModel, SemanticModelFlags, ModuleKind, ModuleSource, NodeId, ScopeId, ScopeKind, SemanticModel, SemanticModelFlags,
StarImport, SubmoduleImport, StarImport, SubmoduleImport,
}; };
use ruff_python_stdlib::builtins::{IPYTHON_BUILTINS, MAGIC_GLOBALS, PYTHON_BUILTINS}; use ruff_python_stdlib::builtins::{python_builtins, IPYTHON_BUILTINS, MAGIC_GLOBALS};
use ruff_python_trivia::CommentRanges; use ruff_python_trivia::CommentRanges;
use ruff_source_file::{Locator, OneIndexed, SourceRow}; use ruff_source_file::{Locator, OneIndexed, SourceRow};
use ruff_text_size::{Ranged, TextRange, TextSize}; use ruff_text_size::{Ranged, TextRange, TextSize};
@ -1912,7 +1912,7 @@ impl<'a> Checker<'a> {
} }
fn bind_builtins(&mut self) { fn bind_builtins(&mut self) {
for builtin in PYTHON_BUILTINS for builtin in python_builtins(self.settings.target_version.minor())
.iter() .iter()
.chain(MAGIC_GLOBALS.iter()) .chain(MAGIC_GLOBALS.iter())
.chain( .chain(

View file

@ -1,12 +1,16 @@
use crate::settings::types::PythonVersion;
use ruff_python_ast::PySourceType; use ruff_python_ast::PySourceType;
use ruff_python_stdlib::builtins::{is_ipython_builtin, is_python_builtin}; use ruff_python_stdlib::builtins::{is_ipython_builtin, is_python_builtin};
pub(super) fn shadows_builtin( pub(super) fn shadows_builtin(
name: &str, name: &str,
ignorelist: &[String],
source_type: PySourceType, source_type: PySourceType,
ignorelist: &[String],
python_version: PythonVersion,
) -> bool { ) -> bool {
if is_python_builtin(name) || source_type.is_ipynb() && is_ipython_builtin(name) { if is_python_builtin(name, python_version.minor())
|| source_type.is_ipynb() && is_ipython_builtin(name)
{
ignorelist.iter().all(|ignore| ignore != name) ignorelist.iter().all(|ignore| ignore != name)
} else { } else {
false false

View file

@ -12,6 +12,7 @@ mod tests {
use crate::assert_messages; use crate::assert_messages;
use crate::registry::Rule; use crate::registry::Rule;
use crate::settings::types::PythonVersion;
use crate::settings::LinterSettings; use crate::settings::LinterSettings;
use crate::test::test_path; use crate::test::test_path;
@ -112,4 +113,18 @@ mod tests {
assert_messages!(snapshot, diagnostics); assert_messages!(snapshot, diagnostics);
Ok(()) Ok(())
} }
#[test_case(Rule::BuiltinImportShadowing, Path::new("A004.py"))]
fn rules_py312(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}_py38", rule_code.noqa_code(), path.to_string_lossy());
let diagnostics = test_path(
Path::new("flake8_builtins").join(path).as_path(),
&LinterSettings {
target_version: PythonVersion::Py38,
..LinterSettings::for_rule(rule_code)
},
)?;
assert_messages!(snapshot, diagnostics);
Ok(())
}
} }

View file

@ -66,8 +66,9 @@ impl Violation for BuiltinArgumentShadowing {
pub(crate) fn builtin_argument_shadowing(checker: &mut Checker, parameter: &Parameter) { pub(crate) fn builtin_argument_shadowing(checker: &mut Checker, parameter: &Parameter) {
if shadows_builtin( if shadows_builtin(
parameter.name.as_str(), parameter.name.as_str(),
&checker.settings.flake8_builtins.builtins_ignorelist,
checker.source_type, checker.source_type,
&checker.settings.flake8_builtins.builtins_ignorelist,
checker.settings.target_version,
) { ) {
// Ignore `@override` and `@overload` decorated functions. // Ignore `@override` and `@overload` decorated functions.
if checker if checker

View file

@ -98,8 +98,9 @@ pub(crate) fn builtin_attribute_shadowing(
if shadows_builtin( if shadows_builtin(
name, name,
&checker.settings.flake8_builtins.builtins_ignorelist,
checker.source_type, checker.source_type,
&checker.settings.flake8_builtins.builtins_ignorelist,
checker.settings.target_version,
) { ) {
// Ignore explicit overrides. // Ignore explicit overrides.
if class_def.decorator_list.iter().any(|decorator| { if class_def.decorator_list.iter().any(|decorator| {

View file

@ -36,8 +36,9 @@ pub(crate) fn builtin_import_shadowing(checker: &mut Checker, alias: &Alias) {
let name = alias.asname.as_ref().unwrap_or(&alias.name); let name = alias.asname.as_ref().unwrap_or(&alias.name);
if shadows_builtin( if shadows_builtin(
name.as_str(), name.as_str(),
&checker.settings.flake8_builtins.builtins_ignorelist,
checker.source_type, checker.source_type,
&checker.settings.flake8_builtins.builtins_ignorelist,
checker.settings.target_version,
) { ) {
checker.diagnostics.push(Diagnostic::new( checker.diagnostics.push(Diagnostic::new(
BuiltinImportShadowing { BuiltinImportShadowing {

View file

@ -42,8 +42,9 @@ pub(crate) fn builtin_lambda_argument_shadowing(checker: &mut Checker, lambda: &
let name = &param.parameter.name; let name = &param.parameter.name;
if shadows_builtin( if shadows_builtin(
name.as_ref(), name.as_ref(),
&checker.settings.flake8_builtins.builtins_ignorelist,
checker.source_type, checker.source_type,
&checker.settings.flake8_builtins.builtins_ignorelist,
checker.settings.target_version,
) { ) {
checker.diagnostics.push(Diagnostic::new( checker.diagnostics.push(Diagnostic::new(
BuiltinLambdaArgumentShadowing { BuiltinLambdaArgumentShadowing {

View file

@ -61,8 +61,9 @@ impl Violation for BuiltinVariableShadowing {
pub(crate) fn builtin_variable_shadowing(checker: &mut Checker, name: &str, range: TextRange) { pub(crate) fn builtin_variable_shadowing(checker: &mut Checker, name: &str, range: TextRange) {
if shadows_builtin( if shadows_builtin(
name, name,
&checker.settings.flake8_builtins.builtins_ignorelist,
checker.source_type, checker.source_type,
&checker.settings.flake8_builtins.builtins_ignorelist,
checker.settings.target_version,
) { ) {
checker.diagnostics.push(Diagnostic::new( checker.diagnostics.push(Diagnostic::new(
BuiltinVariableShadowing { BuiltinVariableShadowing {

View file

@ -52,4 +52,20 @@ A004.py:5:30: A004 Import `dir` is shadowing a Python builtin
4 | from some import input, exec 4 | from some import input, exec
5 | from directory import new as dir 5 | from directory import new as dir
| ^^^ A004 | ^^^ A004
6 |
7 | # See: https://github.com/astral-sh/ruff/issues/13037
| |
A004.py:11:32: A004 Import `BaseExceptionGroup` is shadowing a Python builtin
|
10 | if sys.version_info < (3, 11):
11 | from exceptiongroup import BaseExceptionGroup, ExceptionGroup
| ^^^^^^^^^^^^^^^^^^ A004
|
A004.py:11:52: A004 Import `ExceptionGroup` is shadowing a Python builtin
|
10 | if sys.version_info < (3, 11):
11 | from exceptiongroup import BaseExceptionGroup, ExceptionGroup
| ^^^^^^^^^^^^^^ A004
|

View file

@ -45,3 +45,17 @@ A004.py:4:25: A004 Import `exec` is shadowing a Python builtin
| ^^^^ A004 | ^^^^ A004
5 | from directory import new as dir 5 | from directory import new as dir
| |
A004.py:11:32: A004 Import `BaseExceptionGroup` is shadowing a Python builtin
|
10 | if sys.version_info < (3, 11):
11 | from exceptiongroup import BaseExceptionGroup, ExceptionGroup
| ^^^^^^^^^^^^^^^^^^ A004
|
A004.py:11:52: A004 Import `ExceptionGroup` is shadowing a Python builtin
|
10 | if sys.version_info < (3, 11):
11 | from exceptiongroup import BaseExceptionGroup, ExceptionGroup
| ^^^^^^^^^^^^^^ A004
|

View file

@ -0,0 +1,57 @@
---
source: crates/ruff_linter/src/rules/flake8_builtins/mod.rs
---
A004.py:1:16: A004 Import `sum` is shadowing a Python builtin
|
1 | import some as sum
| ^^^ A004
2 | import float
3 | from some import other as int
|
A004.py:2:8: A004 Import `float` is shadowing a Python builtin
|
1 | import some as sum
2 | import float
| ^^^^^ A004
3 | from some import other as int
4 | from some import input, exec
|
A004.py:3:27: A004 Import `int` is shadowing a Python builtin
|
1 | import some as sum
2 | import float
3 | from some import other as int
| ^^^ A004
4 | from some import input, exec
5 | from directory import new as dir
|
A004.py:4:18: A004 Import `input` is shadowing a Python builtin
|
2 | import float
3 | from some import other as int
4 | from some import input, exec
| ^^^^^ A004
5 | from directory import new as dir
|
A004.py:4:25: A004 Import `exec` is shadowing a Python builtin
|
2 | import float
3 | from some import other as int
4 | from some import input, exec
| ^^^^ A004
5 | from directory import new as dir
|
A004.py:5:30: A004 Import `dir` is shadowing a Python builtin
|
3 | from some import other as int
4 | from some import input, exec
5 | from directory import new as dir
| ^^^ A004
6 |
7 | # See: https://github.com/astral-sh/ruff/issues/13037
|

View file

@ -1,166 +1,3 @@
/// A list of all Python builtins.
///
/// Intended to be kept in sync with [`is_python_builtin`].
pub const PYTHON_BUILTINS: &[&str] = &[
"ArithmeticError",
"AssertionError",
"AttributeError",
"BaseException",
"BaseExceptionGroup",
"BlockingIOError",
"BrokenPipeError",
"BufferError",
"BytesWarning",
"ChildProcessError",
"ConnectionAbortedError",
"ConnectionError",
"ConnectionRefusedError",
"ConnectionResetError",
"DeprecationWarning",
"EOFError",
"Ellipsis",
"EncodingWarning",
"EnvironmentError",
"Exception",
"ExceptionGroup",
"False",
"FileExistsError",
"FileNotFoundError",
"FloatingPointError",
"FutureWarning",
"GeneratorExit",
"IOError",
"ImportError",
"ImportWarning",
"IndentationError",
"IndexError",
"InterruptedError",
"IsADirectoryError",
"KeyError",
"KeyboardInterrupt",
"LookupError",
"MemoryError",
"ModuleNotFoundError",
"NameError",
"None",
"NotADirectoryError",
"NotImplemented",
"NotImplementedError",
"OSError",
"OverflowError",
"PendingDeprecationWarning",
"PermissionError",
"ProcessLookupError",
"RecursionError",
"ReferenceError",
"ResourceWarning",
"RuntimeError",
"RuntimeWarning",
"StopAsyncIteration",
"StopIteration",
"SyntaxError",
"SyntaxWarning",
"SystemError",
"SystemExit",
"TabError",
"TimeoutError",
"True",
"TypeError",
"UnboundLocalError",
"UnicodeDecodeError",
"UnicodeEncodeError",
"UnicodeError",
"UnicodeTranslateError",
"UnicodeWarning",
"UserWarning",
"ValueError",
"Warning",
"ZeroDivisionError",
"__build_class__",
"__debug__",
"__doc__",
"__import__",
"__loader__",
"__name__",
"__package__",
"__spec__",
"abs",
"aiter",
"all",
"anext",
"any",
"ascii",
"bin",
"bool",
"breakpoint",
"bytearray",
"bytes",
"callable",
"chr",
"classmethod",
"compile",
"complex",
"copyright",
"credits",
"delattr",
"dict",
"dir",
"divmod",
"enumerate",
"eval",
"exec",
"exit",
"filter",
"float",
"format",
"frozenset",
"getattr",
"globals",
"hasattr",
"hash",
"help",
"hex",
"id",
"input",
"int",
"isinstance",
"issubclass",
"iter",
"len",
"license",
"list",
"locals",
"map",
"max",
"memoryview",
"min",
"next",
"object",
"oct",
"open",
"ord",
"pow",
"print",
"property",
"quit",
"range",
"repr",
"reversed",
"round",
"set",
"setattr",
"slice",
"sorted",
"staticmethod",
"str",
"sum",
"super",
"tuple",
"type",
"vars",
"zip",
];
/// A list of all builtins that are available in IPython. /// A list of all builtins that are available in IPython.
/// ///
/// How to create this list: /// How to create this list:
@ -186,170 +23,343 @@ pub const MAGIC_GLOBALS: &[&str] = &[
"__file__", "__file__",
]; ];
/// Return the list of builtins for the given Python minor version.
///
/// Intended to be kept in sync with [`is_python_builtin`].
pub fn python_builtins(minor: u8) -> Vec<&'static str> {
let mut builtins = vec![
"ArithmeticError",
"AssertionError",
"AttributeError",
"BaseException",
"BlockingIOError",
"BrokenPipeError",
"BufferError",
"BytesWarning",
"ChildProcessError",
"ConnectionAbortedError",
"ConnectionError",
"ConnectionRefusedError",
"ConnectionResetError",
"DeprecationWarning",
"EOFError",
"Ellipsis",
"EnvironmentError",
"Exception",
"False",
"FileExistsError",
"FileNotFoundError",
"FloatingPointError",
"FutureWarning",
"GeneratorExit",
"IOError",
"ImportError",
"ImportWarning",
"IndentationError",
"IndexError",
"InterruptedError",
"IsADirectoryError",
"KeyError",
"KeyboardInterrupt",
"LookupError",
"MemoryError",
"ModuleNotFoundError",
"NameError",
"None",
"NotADirectoryError",
"NotImplemented",
"NotImplementedError",
"OSError",
"OverflowError",
"PendingDeprecationWarning",
"PermissionError",
"ProcessLookupError",
"RecursionError",
"ReferenceError",
"ResourceWarning",
"RuntimeError",
"RuntimeWarning",
"StopAsyncIteration",
"StopIteration",
"SyntaxError",
"SyntaxWarning",
"SystemError",
"SystemExit",
"TabError",
"TimeoutError",
"True",
"TypeError",
"UnboundLocalError",
"UnicodeDecodeError",
"UnicodeEncodeError",
"UnicodeError",
"UnicodeTranslateError",
"UnicodeWarning",
"UserWarning",
"ValueError",
"Warning",
"ZeroDivisionError",
"__build_class__",
"__debug__",
"__doc__",
"__import__",
"__loader__",
"__name__",
"__package__",
"__spec__",
"abs",
"all",
"any",
"ascii",
"bin",
"bool",
"breakpoint",
"bytearray",
"bytes",
"callable",
"chr",
"classmethod",
"compile",
"complex",
"copyright",
"credits",
"delattr",
"dict",
"dir",
"divmod",
"enumerate",
"eval",
"exec",
"exit",
"filter",
"float",
"format",
"frozenset",
"getattr",
"globals",
"hasattr",
"hash",
"help",
"hex",
"id",
"input",
"int",
"isinstance",
"issubclass",
"iter",
"len",
"license",
"list",
"locals",
"map",
"max",
"memoryview",
"min",
"next",
"object",
"oct",
"open",
"ord",
"pow",
"print",
"property",
"quit",
"range",
"repr",
"reversed",
"round",
"set",
"setattr",
"slice",
"sorted",
"staticmethod",
"str",
"sum",
"super",
"tuple",
"type",
"vars",
"zip",
];
if minor >= 10 {
builtins.extend(vec!["EncodingWarning", "aiter", "anext"]);
}
if minor >= 11 {
builtins.extend(vec!["BaseExceptionGroup", "ExceptionGroup"]);
}
if minor >= 13 {
builtins.extend(vec!["PythonFinalizationError"]);
}
builtins
}
/// Returns `true` if the given name is that of a Python builtin. /// Returns `true` if the given name is that of a Python builtin.
/// ///
/// Intended to be kept in sync with [`PYTHON_BUILTINS`]. /// Intended to be kept in sync with [`python_builtins`].
pub fn is_python_builtin(name: &str) -> bool { pub fn is_python_builtin(name: &str, minor_version: u8) -> bool {
// Constructed by converting the `PYTHON_BUILTINS` slice to a `match` expression.
matches!( matches!(
name, (minor_version, name),
"ArithmeticError" (
| "AssertionError" _,
| "AttributeError" "ArithmeticError"
| "BaseException" | "AssertionError"
| "BaseExceptionGroup" | "AttributeError"
| "BlockingIOError" | "BaseException"
| "BrokenPipeError" | "BlockingIOError"
| "BufferError" | "BrokenPipeError"
| "BytesWarning" | "BufferError"
| "ChildProcessError" | "BytesWarning"
| "ConnectionAbortedError" | "ChildProcessError"
| "ConnectionError" | "ConnectionAbortedError"
| "ConnectionRefusedError" | "ConnectionError"
| "ConnectionResetError" | "ConnectionRefusedError"
| "DeprecationWarning" | "ConnectionResetError"
| "EOFError" | "DeprecationWarning"
| "Ellipsis" | "EOFError"
| "EncodingWarning" | "Ellipsis"
| "EnvironmentError" | "EnvironmentError"
| "Exception" | "Exception"
| "ExceptionGroup" | "False"
| "False" | "FileExistsError"
| "FileExistsError" | "FileNotFoundError"
| "FileNotFoundError" | "FloatingPointError"
| "FloatingPointError" | "FutureWarning"
| "FutureWarning" | "GeneratorExit"
| "GeneratorExit" | "IOError"
| "IOError" | "ImportError"
| "ImportError" | "ImportWarning"
| "ImportWarning" | "IndentationError"
| "IndentationError" | "IndexError"
| "IndexError" | "InterruptedError"
| "InterruptedError" | "IsADirectoryError"
| "IsADirectoryError" | "KeyError"
| "KeyError" | "KeyboardInterrupt"
| "KeyboardInterrupt" | "LookupError"
| "LookupError" | "MemoryError"
| "MemoryError" | "ModuleNotFoundError"
| "ModuleNotFoundError" | "NameError"
| "NameError" | "None"
| "None" | "NotADirectoryError"
| "NotADirectoryError" | "NotImplemented"
| "NotImplemented" | "NotImplementedError"
| "NotImplementedError" | "OSError"
| "OSError" | "OverflowError"
| "OverflowError" | "PendingDeprecationWarning"
| "PendingDeprecationWarning" | "PermissionError"
| "PermissionError" | "ProcessLookupError"
| "ProcessLookupError" | "RecursionError"
| "RecursionError" | "ReferenceError"
| "ReferenceError" | "ResourceWarning"
| "ResourceWarning" | "RuntimeError"
| "RuntimeError" | "RuntimeWarning"
| "RuntimeWarning" | "StopAsyncIteration"
| "StopAsyncIteration" | "StopIteration"
| "StopIteration" | "SyntaxError"
| "SyntaxError" | "SyntaxWarning"
| "SyntaxWarning" | "SystemError"
| "SystemError" | "SystemExit"
| "SystemExit" | "TabError"
| "TabError" | "TimeoutError"
| "TimeoutError" | "True"
| "True" | "TypeError"
| "TypeError" | "UnboundLocalError"
| "UnboundLocalError" | "UnicodeDecodeError"
| "UnicodeDecodeError" | "UnicodeEncodeError"
| "UnicodeEncodeError" | "UnicodeError"
| "UnicodeError" | "UnicodeTranslateError"
| "UnicodeTranslateError" | "UnicodeWarning"
| "UnicodeWarning" | "UserWarning"
| "UserWarning" | "ValueError"
| "ValueError" | "Warning"
| "Warning" | "ZeroDivisionError"
| "ZeroDivisionError" | "__build_class__"
| "__build_class__" | "__debug__"
| "__debug__" | "__doc__"
| "__doc__" | "__import__"
| "__import__" | "__loader__"
| "__loader__" | "__name__"
| "__name__" | "__package__"
| "__package__" | "__spec__"
| "__spec__" | "abs"
| "abs" | "all"
| "aiter" | "any"
| "all" | "ascii"
| "anext" | "bin"
| "any" | "bool"
| "ascii" | "breakpoint"
| "bin" | "bytearray"
| "bool" | "bytes"
| "breakpoint" | "callable"
| "bytearray" | "chr"
| "bytes" | "classmethod"
| "callable" | "compile"
| "chr" | "complex"
| "classmethod" | "copyright"
| "compile" | "credits"
| "complex" | "delattr"
| "copyright" | "dict"
| "credits" | "dir"
| "delattr" | "divmod"
| "dict" | "enumerate"
| "dir" | "eval"
| "divmod" | "exec"
| "enumerate" | "exit"
| "eval" | "filter"
| "exec" | "float"
| "exit" | "format"
| "filter" | "frozenset"
| "float" | "getattr"
| "format" | "globals"
| "frozenset" | "hasattr"
| "getattr" | "hash"
| "globals" | "help"
| "hasattr" | "hex"
| "hash" | "id"
| "help" | "input"
| "hex" | "int"
| "id" | "isinstance"
| "input" | "issubclass"
| "int" | "iter"
| "isinstance" | "len"
| "issubclass" | "license"
| "iter" | "list"
| "len" | "locals"
| "license" | "map"
| "list" | "max"
| "locals" | "memoryview"
| "map" | "min"
| "max" | "next"
| "memoryview" | "object"
| "min" | "oct"
| "next" | "open"
| "object" | "ord"
| "oct" | "pow"
| "open" | "print"
| "ord" | "property"
| "pow" | "quit"
| "print" | "range"
| "property" | "repr"
| "quit" | "reversed"
| "range" | "round"
| "repr" | "set"
| "reversed" | "setattr"
| "round" | "slice"
| "set" | "sorted"
| "setattr" | "staticmethod"
| "slice" | "str"
| "sorted" | "sum"
| "staticmethod" | "super"
| "str" | "tuple"
| "sum" | "type"
| "super" | "vars"
| "tuple" | "zip"
| "type" ) | (10..=13, "EncodingWarning" | "aiter" | "anext")
| "vars" | (11..=13, "BaseExceptionGroup" | "ExceptionGroup")
| "zip" | (13, "PythonFinalizationError")
) )
} }