[`pyupgrade`] Handle micro version numbers correctly (`UP036`) (#16091)

## Summary

Resolves #16082.

`UP036` will now also take into consideration whether or not a micro
version number is set:

* If a third element doesn't exist, the existing logic is preserved.
* If it exists but is not an integer literal, the check will not be
reported.
* If it is an integer literal but doesn't fit into a `u8`, the check
will be reported as invalid.
* Otherwise, the compared version is determined to always be less than
the target version when:
	* The target's minor version is smaller than that of the comparator, or
* The operator is `<`, the micro version is 0, and the two minor
versions compare equal.

As this is considered a bugfix, it is not preview-gated.

## Test Plan

`cargo nextest run` and `cargo insta test`.

---------

Co-authored-by: Micha Reiser <micha@reiser.io>
This commit is contained in:
InSync 2025-02-11 14:40:56 +07:00 committed by GitHub
parent 0019d39f6e
commit 7fbd89cb39
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 150 additions and 3 deletions

View File

@ -28,3 +28,46 @@ else:
else: else:
print(3) print(3)
return None return None
# https://github.com/astral-sh/ruff/issues/16082
## Errors
if sys.version_info < (3, 12, 0):
print()
if sys.version_info <= (3, 12, 0):
print()
if sys.version_info < (3, 12, 11):
print()
if sys.version_info < (3, 13, 0):
print()
if sys.version_info <= (3, 13, 100000):
print()
## No errors
if sys.version_info <= (3, 13, foo):
print()
if sys.version_info <= (3, 13, 'final'):
print()
if sys.version_info <= (3, 13, 0):
print()
if sys.version_info < (3, 13, 37):
print()
if sys.version_info <= (3, 13, 37):
print()
if sys.version_info <= (3, 14, 0):
print()
if sys.version_info <= (3, 14, 15):
print()

View File

@ -236,14 +236,27 @@ fn version_always_less_than(
return Err(anyhow::anyhow!("invalid minor version: {if_minor}")); return Err(anyhow::anyhow!("invalid minor version: {if_minor}"));
}; };
let if_micro = match check_version_iter.next() {
None => None,
Some(micro) => match micro.as_u8() {
Some(micro) => Some(micro),
None => anyhow::bail!("invalid micro version: {micro}"),
},
};
Ok(if or_equal { Ok(if or_equal {
// Ex) `sys.version_info <= 3.8`. If Python 3.8 is the minimum supported version, // Ex) `sys.version_info <= 3.8`. If Python 3.8 is the minimum supported version,
// the condition won't always evaluate to `false`, so we want to return `false`. // the condition won't always evaluate to `false`, so we want to return `false`.
if_minor < py_minor if_minor < py_minor
} else { } else {
// Ex) `sys.version_info < 3.8`. If Python 3.8 is the minimum supported version, if let Some(if_micro) = if_micro {
// the condition _will_ always evaluate to `false`, so we want to return `true`. // Ex) `sys.version_info < 3.8.3`
if_minor <= py_minor if_minor < py_minor || if_minor == py_minor && if_micro == 0
} else {
// Ex) `sys.version_info < 3.8`. If Python 3.8 is the minimum supported version,
// the condition _will_ always evaluate to `false`, so we want to return `true`.
if_minor <= py_minor
}
}) })
} }
} }

View File

@ -66,3 +66,94 @@ UP036_5.py:18:4: UP036 [*] Version block is outdated for minimum Python version
23 |+ else: 23 |+ else:
24 |+ print(3) 24 |+ print(3)
25 |+ return None 25 |+ return None
31 26 |
32 27 |
33 28 | # https://github.com/astral-sh/ruff/issues/16082
UP036_5.py:36:4: UP036 [*] Version block is outdated for minimum Python version
|
35 | ## Errors
36 | if sys.version_info < (3, 12, 0):
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP036
37 | print()
|
= help: Remove outdated version block
Unsafe fix
33 33 | # https://github.com/astral-sh/ruff/issues/16082
34 34 |
35 35 | ## Errors
36 |-if sys.version_info < (3, 12, 0):
37 |- print()
38 36 |
39 37 | if sys.version_info <= (3, 12, 0):
40 38 | print()
UP036_5.py:39:4: UP036 [*] Version block is outdated for minimum Python version
|
37 | print()
38 |
39 | if sys.version_info <= (3, 12, 0):
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP036
40 | print()
|
= help: Remove outdated version block
Unsafe fix
36 36 | if sys.version_info < (3, 12, 0):
37 37 | print()
38 38 |
39 |-if sys.version_info <= (3, 12, 0):
40 |- print()
41 39 |
42 40 | if sys.version_info < (3, 12, 11):
43 41 | print()
UP036_5.py:42:4: UP036 [*] Version block is outdated for minimum Python version
|
40 | print()
41 |
42 | if sys.version_info < (3, 12, 11):
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP036
43 | print()
|
= help: Remove outdated version block
Unsafe fix
39 39 | if sys.version_info <= (3, 12, 0):
40 40 | print()
41 41 |
42 |-if sys.version_info < (3, 12, 11):
43 |- print()
44 42 |
45 43 | if sys.version_info < (3, 13, 0):
46 44 | print()
UP036_5.py:45:4: UP036 [*] Version block is outdated for minimum Python version
|
43 | print()
44 |
45 | if sys.version_info < (3, 13, 0):
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP036
46 | print()
|
= help: Remove outdated version block
Unsafe fix
42 42 | if sys.version_info < (3, 12, 11):
43 43 | print()
44 44 |
45 |-if sys.version_info < (3, 13, 0):
46 |- print()
47 45 |
48 46 | if sys.version_info <= (3, 13, 100000):
49 47 | print()
UP036_5.py:48:24: UP036 Version specifier is invalid
|
46 | print()
47 |
48 | if sys.version_info <= (3, 13, 100000):
| ^^^^^^^^^^^^^^^ UP036
49 | print()
|