[pyupgrade] Extend version detection to include sys.version_info.major (UP036) (#18633)
Some checks are pending
CI / cargo build (msrv) (push) Blocked by required conditions
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo clippy (push) Blocked by required conditions
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / cargo test (windows) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (release) (push) Waiting to run
CI / cargo fuzz build (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / ecosystem (push) Blocked by required conditions
CI / Fuzz for new ty panics (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / pre-commit (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / test ruff-lsp (push) Blocked by required conditions
CI / check playground (push) Blocked by required conditions
CI / benchmarks-instrumented (push) Blocked by required conditions
CI / benchmarks-walltime (push) Blocked by required conditions
[ty Playground] Release / publish (push) Waiting to run

## Summary

Resolves #18165 

Added pattern `["sys", "version_info", "major"]` to the existing matches
for `sys.version_info` to ensure consistent handling of both the base
object and its major version attribute.

## Test Plan
`cargo nextest run` and `cargo insta test`

---------

Co-authored-by: Brent Westbrook <brentrwestbrook@gmail.com>
This commit is contained in:
Igor Drokin 2025-06-23 23:01:55 +03:00 committed by GitHub
parent 885dc9091f
commit da16e00751
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 181 additions and 8 deletions

View file

@ -71,3 +71,47 @@ if sys.version_info <= (3, 14, 0):
if sys.version_info <= (3, 14, 15): if sys.version_info <= (3, 14, 15):
print() print()
# https://github.com/astral-sh/ruff/issues/18165
if sys.version_info.major >= 3:
print("3")
else:
print("2")
if sys.version_info.major > 3:
print("3")
else:
print("2")
if sys.version_info.major <= 3:
print("3")
else:
print("2")
if sys.version_info.major < 3:
print("3")
else:
print("2")
if sys.version_info.major == 3:
print("3")
else:
print("2")
# Semantically incorrect, skip fixing
if sys.version_info.major[1] > 3:
print(3)
else:
print(2)
if sys.version_info.major > (3, 13):
print(3)
else:
print(2)
if sys.version_info.major[:2] > (3, 13):
print(3)
else:
print(2)

View file

@ -14,6 +14,7 @@ use crate::checkers::ast::Checker;
use crate::fix::edits::{adjust_indentation, delete_stmt}; use crate::fix::edits::{adjust_indentation, delete_stmt};
use crate::{Edit, Fix, FixAvailability, Violation}; use crate::{Edit, Fix, FixAvailability, Violation};
use ruff_python_ast::PythonVersion; use ruff_python_ast::PythonVersion;
use ruff_python_semantic::SemanticModel;
/// ## What it does /// ## What it does
/// Checks for conditional blocks gated on `sys.version_info` comparisons /// Checks for conditional blocks gated on `sys.version_info` comparisons
@ -103,14 +104,7 @@ pub(crate) fn outdated_version_block(checker: &Checker, stmt_if: &StmtIf) {
continue; continue;
}; };
// Detect `sys.version_info`, along with slices (like `sys.version_info[:2]`). if !is_valid_version_info(checker.semantic(), left) {
if !checker
.semantic()
.resolve_qualified_name(map_subscript(left))
.is_some_and(|qualified_name| {
matches!(qualified_name.segments(), ["sys", "version_info"])
})
{
continue; continue;
} }
@ -456,6 +450,21 @@ fn extract_version(elts: &[Expr]) -> Option<Vec<Int>> {
Some(version) Some(version)
} }
/// Returns `true` if the expression is related to `sys.version_info`.
///
/// This includes:
/// - Direct access: `sys.version_info`
/// - Subscript access: `sys.version_info[:2]`, `sys.version_info[0]`
/// - Major version attribute: `sys.version_info.major`
fn is_valid_version_info(semantic: &SemanticModel, left: &Expr) -> bool {
semantic
.resolve_qualified_name(map_subscript(left))
.is_some_and(|name| matches!(name.segments(), ["sys", "version_info"]))
|| semantic
.resolve_qualified_name(left)
.is_some_and(|name| matches!(name.segments(), ["sys", "version_info", "major"]))
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use test_case::test_case; use test_case::test_case;

View file

@ -157,3 +157,123 @@ UP036_5.py:48:24: UP036 Version specifier is invalid
| ^^^^^^^^^^^^^^^ UP036 | ^^^^^^^^^^^^^^^ UP036
49 | print() 49 | print()
| |
UP036_5.py:77:4: UP036 [*] Version block is outdated for minimum Python version
|
75 | # https://github.com/astral-sh/ruff/issues/18165
76 |
77 | if sys.version_info.major >= 3:
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP036
78 | print("3")
79 | else:
|
= help: Remove outdated version block
Unsafe fix
74 74 |
75 75 | # https://github.com/astral-sh/ruff/issues/18165
76 76 |
77 |-if sys.version_info.major >= 3:
78 |- print("3")
79 |-else:
80 |- print("2")
77 |+print("3")
81 78 |
82 79 | if sys.version_info.major > 3:
83 80 | print("3")
UP036_5.py:82:4: UP036 [*] Version block is outdated for minimum Python version
|
80 | print("2")
81 |
82 | if sys.version_info.major > 3:
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ UP036
83 | print("3")
84 | else:
|
= help: Remove outdated version block
Unsafe fix
79 79 | else:
80 80 | print("2")
81 81 |
82 |-if sys.version_info.major > 3:
83 |- print("3")
84 |-else:
85 |- print("2")
82 |+print("2")
86 83 |
87 84 | if sys.version_info.major <= 3:
88 85 | print("3")
UP036_5.py:87:4: UP036 [*] Version block is outdated for minimum Python version
|
85 | print("2")
86 |
87 | if sys.version_info.major <= 3:
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP036
88 | print("3")
89 | else:
|
= help: Remove outdated version block
Unsafe fix
84 84 | else:
85 85 | print("2")
86 86 |
87 |-if sys.version_info.major <= 3:
88 |- print("3")
89 |-else:
90 |- print("2")
87 |+print("3")
91 88 |
92 89 | if sys.version_info.major < 3:
93 90 | print("3")
UP036_5.py:92:4: UP036 [*] Version block is outdated for minimum Python version
|
90 | print("2")
91 |
92 | if sys.version_info.major < 3:
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ UP036
93 | print("3")
94 | else:
|
= help: Remove outdated version block
Unsafe fix
89 89 | else:
90 90 | print("2")
91 91 |
92 |-if sys.version_info.major < 3:
93 |- print("3")
94 |-else:
95 |- print("2")
92 |+print("2")
96 93 |
97 94 | if sys.version_info.major == 3:
98 95 | print("3")
UP036_5.py:97:4: UP036 [*] Version block is outdated for minimum Python version
|
95 | print("2")
96 |
97 | if sys.version_info.major == 3:
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP036
98 | print("3")
99 | else:
|
= help: Remove outdated version block
Unsafe fix
94 94 | else:
95 95 | print("2")
96 96 |
97 |-if sys.version_info.major == 3:
98 |- print("3")
99 |-else:
100 |- print("2")
97 |+print("3")
101 98 |
102 99 | # Semantically incorrect, skip fixing
103 100 |