From 8db8fb2844acfd1bd611eb80012d044910cf63ca Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Fri, 30 Aug 2024 12:29:14 -0400 Subject: [PATCH] Use dynamic builtins list based on Python version --- builtins.py | 69 ++ .../test/fixtures/flake8_builtins/A004.py | 6 + .../test/fixtures/pylint/no_self_use.py | 20 + crates/ruff_linter/src/checkers/ast/mod.rs | 4 +- .../src/rules/flake8_builtins/helpers.rs | 8 +- .../src/rules/flake8_builtins/mod.rs | 15 + .../rules/builtin_argument_shadowing.rs | 3 +- .../rules/builtin_attribute_shadowing.rs | 3 +- .../rules/builtin_import_shadowing.rs | 3 +- .../builtin_lambda_argument_shadowing.rs | 3 +- .../rules/builtin_variable_shadowing.rs | 3 +- ..._flake8_builtins__tests__A004_A004.py.snap | 2 + ...8_builtins__tests__A004_A004.py_py312.snap | 71 ++ .../src/rules/pylint/rules/no_self_use.rs | 1 + ...pylint__tests__PLR6301_no_self_use.py.snap | 9 +- .../src/analyze/visibility.rs | 23 +- crates/ruff_python_stdlib/src/builtins.rs | 656 +++++++++--------- 17 files changed, 565 insertions(+), 334 deletions(-) create mode 100644 builtins.py create mode 100644 crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A004_A004.py_py312.snap diff --git a/builtins.py b/builtins.py new file mode 100644 index 00000000000000..8ddae7937042f3 --- /dev/null +++ b/builtins.py @@ -0,0 +1,69 @@ +import subprocess +import json + +def enumerate_builtins_by_version(python_version): + cmd = [ + "uv", "run", "--python", f"{python_version}", "--no-project", "python", + "-c", + """ +import sys +import builtins +import json + +python_version = f"{sys.version_info.major}.{sys.version_info.minor}" +builtin_list = sorted(dir(builtins)) + +print(json.dumps(builtin_list)) + """ + ] + result = subprocess.run(cmd, check=True, capture_output=True, text=True) + return json.loads(result.stdout) + +if __name__ == "__main__": + python_versions = ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] + all_results = {} + for version in python_versions: + print(f"\nRunning for Python {version}") + result = enumerate_builtins_by_version(version) + all_results[version] = result + + print("\nRust code for listing builtins:") + print("match (major, minor) {") + for version, builtins in all_results.items(): + major, minor = version.split('.') + print(f" ({major}, {minor}) => &[") + for builtin in builtins: + print(f' "{builtin}",') + print(" ],") + print(" _ => &[],") + print("}") + + print("\nRust code for checking if a string is a builtin:") + print("matches!(minor, {") + + # Collect all unique builtins across versions + all_builtins = set() + for builtins in all_results.values(): + all_builtins.update(builtins) + + print("pub fn is_known_standard_library(minor_version: u8, module: &str) -> bool {") + print(" matches!(") + print(" (minor_version, module),") + print(" (") + + # Print the builtins that are common to all versions + common_builtins = set.intersection(*map(set, all_results.values())) + for builtin in sorted(common_builtins): + print(f' (_, "{builtin}"),') + + # Print version-specific builtins + for version, builtins in all_results.items(): + _, minor = version.split('.') + version_specific = set(builtins) - common_builtins + if version_specific: + for builtin in sorted(version_specific): + print(f' ({minor}, "{builtin}"),') + + print(" )") + print(" )") + print("}") diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_builtins/A004.py b/crates/ruff_linter/resources/test/fixtures/flake8_builtins/A004.py index ed07e5502294dc..a5cf57ff553e71 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_builtins/A004.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_builtins/A004.py @@ -3,3 +3,9 @@ from some import other as int from some import input, exec 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 diff --git a/crates/ruff_linter/resources/test/fixtures/pylint/no_self_use.py b/crates/ruff_linter/resources/test/fixtures/pylint/no_self_use.py index db74e179c71c16..bbb966aba42da1 100644 --- a/crates/ruff_linter/resources/test/fixtures/pylint/no_self_use.py +++ b/crates/ruff_linter/resources/test/fixtures/pylint/no_self_use.py @@ -83,3 +83,23 @@ def bar(self): def baz(self): if super().foo(): ... + + +# See: https://github.com/astral-sh/ruff/issues/12568 +from attrs import define, field + + +@define +class Foo: + x: int = field() + y: int + + @x.validator + def validate_x(self, attribute, value): + if value <= 0: + raise ValueError("x must be a positive integer") + + @y.validator + def validate_y(self, attribute, value): + if value <= 0: + raise ValueError("y must be a positive integer") diff --git a/crates/ruff_linter/src/checkers/ast/mod.rs b/crates/ruff_linter/src/checkers/ast/mod.rs index 814f2d9f38fab1..7dc7f168eb1b49 100644 --- a/crates/ruff_linter/src/checkers/ast/mod.rs +++ b/crates/ruff_linter/src/checkers/ast/mod.rs @@ -55,7 +55,7 @@ use ruff_python_semantic::{ ModuleKind, ModuleSource, NodeId, ScopeId, ScopeKind, SemanticModel, SemanticModelFlags, 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_source_file::{Locator, OneIndexed, SourceRow}; use ruff_text_size::{Ranged, TextRange, TextSize}; @@ -1912,7 +1912,7 @@ impl<'a> Checker<'a> { } fn bind_builtins(&mut self) { - for builtin in PYTHON_BUILTINS + for builtin in python_builtins(self.settings.target_version.minor()) .iter() .chain(MAGIC_GLOBALS.iter()) .chain( diff --git a/crates/ruff_linter/src/rules/flake8_builtins/helpers.rs b/crates/ruff_linter/src/rules/flake8_builtins/helpers.rs index 54f601b9c9a5bd..552091ba3cee7a 100644 --- a/crates/ruff_linter/src/rules/flake8_builtins/helpers.rs +++ b/crates/ruff_linter/src/rules/flake8_builtins/helpers.rs @@ -1,12 +1,16 @@ +use crate::settings::types::PythonVersion; use ruff_python_ast::PySourceType; use ruff_python_stdlib::builtins::{is_ipython_builtin, is_python_builtin}; pub(super) fn shadows_builtin( name: &str, - ignorelist: &[String], source_type: PySourceType, + ignorelist: &[String], + python_version: PythonVersion, ) -> 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) } else { false diff --git a/crates/ruff_linter/src/rules/flake8_builtins/mod.rs b/crates/ruff_linter/src/rules/flake8_builtins/mod.rs index 8a35abb23d5bef..771d532d71d47f 100644 --- a/crates/ruff_linter/src/rules/flake8_builtins/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_builtins/mod.rs @@ -12,6 +12,7 @@ mod tests { use crate::assert_messages; use crate::registry::Rule; + use crate::settings::types::PythonVersion; use crate::settings::LinterSettings; use crate::test::test_path; @@ -112,4 +113,18 @@ mod tests { assert_messages!(snapshot, diagnostics); Ok(()) } + + #[test_case(Rule::BuiltinImportShadowing, Path::new("A004.py"))] + fn rules_py312(rule_code: Rule, path: &Path) -> Result<()> { + let snapshot = format!("{}_{}_py312", rule_code.noqa_code(), path.to_string_lossy()); + let diagnostics = test_path( + Path::new("flake8_builtins").join(path).as_path(), + &LinterSettings { + target_version: PythonVersion::Py312, + ..LinterSettings::for_rule(rule_code) + }, + )?; + assert_messages!(snapshot, diagnostics); + Ok(()) + } } diff --git a/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_argument_shadowing.rs b/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_argument_shadowing.rs index e8337dadbccb02..275df8c8df0f23 100644 --- a/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_argument_shadowing.rs +++ b/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_argument_shadowing.rs @@ -66,8 +66,9 @@ impl Violation for BuiltinArgumentShadowing { pub(crate) fn builtin_argument_shadowing(checker: &mut Checker, parameter: &Parameter) { if shadows_builtin( parameter.name.as_str(), - &checker.settings.flake8_builtins.builtins_ignorelist, checker.source_type, + &checker.settings.flake8_builtins.builtins_ignorelist, + checker.settings.target_version, ) { // Ignore `@override` and `@overload` decorated functions. if checker diff --git a/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_attribute_shadowing.rs b/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_attribute_shadowing.rs index 124a1bd574aac8..179471ab650a7d 100644 --- a/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_attribute_shadowing.rs +++ b/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_attribute_shadowing.rs @@ -98,8 +98,9 @@ pub(crate) fn builtin_attribute_shadowing( if shadows_builtin( name, - &checker.settings.flake8_builtins.builtins_ignorelist, checker.source_type, + &checker.settings.flake8_builtins.builtins_ignorelist, + checker.settings.target_version, ) { // Ignore explicit overrides. if class_def.decorator_list.iter().any(|decorator| { diff --git a/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_import_shadowing.rs b/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_import_shadowing.rs index 2c601a48af97a3..6e11a7bce465dc 100644 --- a/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_import_shadowing.rs +++ b/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_import_shadowing.rs @@ -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); if shadows_builtin( name.as_str(), - &checker.settings.flake8_builtins.builtins_ignorelist, checker.source_type, + &checker.settings.flake8_builtins.builtins_ignorelist, + checker.settings.target_version, ) { checker.diagnostics.push(Diagnostic::new( BuiltinImportShadowing { diff --git a/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_lambda_argument_shadowing.rs b/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_lambda_argument_shadowing.rs index 446e3f2ddec82f..f8f8756df1c9de 100644 --- a/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_lambda_argument_shadowing.rs +++ b/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_lambda_argument_shadowing.rs @@ -42,8 +42,9 @@ pub(crate) fn builtin_lambda_argument_shadowing(checker: &mut Checker, lambda: & let name = ¶m.parameter.name; if shadows_builtin( name.as_ref(), - &checker.settings.flake8_builtins.builtins_ignorelist, checker.source_type, + &checker.settings.flake8_builtins.builtins_ignorelist, + checker.settings.target_version, ) { checker.diagnostics.push(Diagnostic::new( BuiltinLambdaArgumentShadowing { diff --git a/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_variable_shadowing.rs b/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_variable_shadowing.rs index 1654b59422760e..5dc661dc409cc7 100644 --- a/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_variable_shadowing.rs +++ b/crates/ruff_linter/src/rules/flake8_builtins/rules/builtin_variable_shadowing.rs @@ -61,8 +61,9 @@ impl Violation for BuiltinVariableShadowing { pub(crate) fn builtin_variable_shadowing(checker: &mut Checker, name: &str, range: TextRange) { if shadows_builtin( name, - &checker.settings.flake8_builtins.builtins_ignorelist, checker.source_type, + &checker.settings.flake8_builtins.builtins_ignorelist, + checker.settings.target_version, ) { checker.diagnostics.push(Diagnostic::new( BuiltinVariableShadowing { diff --git a/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A004_A004.py.snap b/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A004_A004.py.snap index a6457721464339..e33ad847bbc282 100644 --- a/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A004_A004.py.snap +++ b/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A004_A004.py.snap @@ -52,4 +52,6 @@ A004.py:5:30: A004 Import `dir` is shadowing a Python builtin 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 | diff --git a/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A004_A004.py_py312.snap b/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A004_A004.py_py312.snap new file mode 100644 index 00000000000000..c31cebd70b7f06 --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_builtins/snapshots/ruff_linter__rules__flake8_builtins__tests__A004_A004.py_py312.snap @@ -0,0 +1,71 @@ +--- +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 + | + +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 + | diff --git a/crates/ruff_linter/src/rules/pylint/rules/no_self_use.rs b/crates/ruff_linter/src/rules/pylint/rules/no_self_use.rs index 19420e7b4fc748..62c11b83c03108 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/no_self_use.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/no_self_use.rs @@ -87,6 +87,7 @@ pub(crate) fn no_self_use( || visibility::is_override(decorator_list, semantic) || visibility::is_overload(decorator_list, semantic) || visibility::is_property(decorator_list, extra_property_decorators, semantic) + || visibility::is_validator(decorator_list, semantic) { return; } diff --git a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLR6301_no_self_use.py.snap b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLR6301_no_self_use.py.snap index eef3c04e36c812..fe3f47b21f7e5e 100644 --- a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLR6301_no_self_use.py.snap +++ b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLR6301_no_self_use.py.snap @@ -27,4 +27,11 @@ no_self_use.py:13:9: PLR6301 Method `greeting_2` could be a function, class meth 14 | print("Hi!") | - +no_self_use.py:103:9: PLR6301 Method `validate_y` could be a function, class method, or static method + | +102 | @y.validator +103 | def validate_y(self, attribute, value): + | ^^^^^^^^^^ PLR6301 +104 | if value <= 0: +105 | raise ValueError("y must be a positive integer") + | diff --git a/crates/ruff_python_semantic/src/analyze/visibility.rs b/crates/ruff_python_semantic/src/analyze/visibility.rs index e28e13d338f7e8..a9173f25c72ef5 100644 --- a/crates/ruff_python_semantic/src/analyze/visibility.rs +++ b/crates/ruff_python_semantic/src/analyze/visibility.rs @@ -1,4 +1,4 @@ -use ruff_python_ast::{self as ast, Decorator}; +use ruff_python_ast::{self as ast, Decorator, Expr}; use ruff_python_ast::helpers::map_callable; use ruff_python_ast::name::{QualifiedName, UnqualifiedName}; @@ -90,6 +90,27 @@ where }) } +/// Returns `true` if a function definition is an `attrs`-like validator based on its decorators. +pub fn is_validator(decorator_list: &[Decorator], semantic: &SemanticModel) -> bool { + decorator_list.iter().any(|decorator| { + let Expr::Attribute(ast::ExprAttribute { value, attr, .. }) = &decorator.expression else { + return false; + }; + + if attr.as_str() != "validator" { + return false; + } + + let Expr::Name(value) = value.as_ref() else { + return false; + }; + + semantic + .resolve_name(value) + .is_some_and(|id| semantic.binding(id).kind.is_assignment()) + }) +} + /// Returns `true` if a class is an `final`. pub fn is_final(decorator_list: &[Decorator], semantic: &SemanticModel) -> bool { decorator_list diff --git a/crates/ruff_python_stdlib/src/builtins.rs b/crates/ruff_python_stdlib/src/builtins.rs index 19ce5cfe116c3c..bc06f8a3425b16 100644 --- a/crates/ruff_python_stdlib/src/builtins.rs +++ b/crates/ruff_python_stdlib/src/builtins.rs @@ -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. /// /// How to create this list: @@ -186,170 +23,343 @@ pub const MAGIC_GLOBALS: &[&str] = &[ "__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!["IncompleteInputError", "PythonFinalizationError"]); + } + + builtins +} + /// Returns `true` if the given name is that of a Python builtin. /// /// Intended to be kept in sync with [`PYTHON_BUILTINS`]. -pub fn is_python_builtin(name: &str) -> bool { - // Constructed by converting the `PYTHON_BUILTINS` slice to a `match` expression. +pub fn is_python_builtin(name: &str, minor_version: u8) -> bool { matches!( - name, - "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" + (minor_version, name), + ( + _, + "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" + ) | (10..=13, "EncodingWarning" | "aiter" | "anext") + | (11..=13, "BaseExceptionGroup" | "ExceptionGroup") + | (13, "IncompleteInputError" | "PythonFinalizationError") ) }