Skip to content

Conversation

Pierre-Sassoulas
Copy link
Member

Type of Changes

Type
βœ“ πŸ› Bug fix
βœ“ πŸ“œ Docs

Description

See astral-sh/ruff#11421 for initial reasoning behind this. I think we should modify the way we compute match case. But not sure if we should add a new visit method in pylint or add a get_children method for match case in astroid so we ends up not having to modify anything on the pylint side (and would benefit from it elsewhere too ?).

@Pierre-Sassoulas Pierre-Sassoulas added Discussion πŸ€” Needs specification πŸ” Accepted as a potential improvement, and needs to specify edge cases, message names, etc. labels May 22, 2024
@Pierre-Sassoulas Pierre-Sassoulas requested a review from cdce8p May 22, 2024 07:22

This comment has been minimized.

@cdce8p
Copy link
Member

cdce8p commented Jun 8, 2024

But not sure if we should add a new visit method in pylint or add a get_children method for match case in astroid so we ends up not having to modify anything on the pylint side (and would benefit from it elsewhere too ?).

get_children should already work same as visitMatchCase. I haven't looked into the mccabe extension yet, so not sure I can really help with that at this point.

@cdce8p cdce8p removed their request for review June 8, 2024 08:37

This comment has been minimized.

@cdce8p
Copy link
Member

cdce8p commented Sep 7, 2025

Cross posting #10542 (comment) here.

IMO match in Python is basically an enhanced if statement. So checks should treat it similarly. AFAICT that was also your opinion on the ruff issue, unless I got that wrong.

I.e. each match case should probably increase the complexity by one.

But not sure if we should add a new visit method in pylint or add a get_children method for match case in astroid so we ends up not having to modify anything on the pylint side (and would benefit from it elsewhere too ?).

Astroid get_children() should already work just fine. We'll likely need to add some custom logic to the mccabe extension.

@Pierre-Sassoulas
Copy link
Member Author

Some are asking for the possibility to configure it on the ruff side here : astral-sh/ruff#11421 (comment)

I wish the consideration for match-case statement for PLR rules was configurable to be turned off with lint.pylint setting. Long match-case statements are very common while walking abstract syntax trees and are arguably way less complex than any other solution.

I'm thinking that they possibly want to make match case that are too complex without having to think about design too much (never saw a > 5 case match statement in pylint, probably because we use the visitor pattern). But at the same time pylint is generally very configurable so why not in this case ?

What do you think @DanielNoord @jacobtylerwalls ?

@cdce8p
Copy link
Member

cdce8p commented Sep 7, 2025

I'm thinking that they possibly want to make match case that are too complex without having to think about design too much (never saw a > 5 case match statement in pylint, probably because we use the visitor pattern). But at the same time pylint is generally very configurable so why not in this case ?

Arguably the configurability is also one its the downsides. Nevertheless good defaults go a long way. So why not. I do think though this should only apply to the too-complex check and not for too-many-branches.

@Pierre-Sassoulas
Copy link
Member Author

Arguably the configurability is also one its the downsides. Nevertheless good defaults go a long way.

Yeah I agree with both points. Slightly in favor of adding the option because at this point it's a little hard to rebrand pylint as an opinionated turn-key linter, even if we wanted that. And I strongly think that the default should be that each "case", count as an edge in the McCabe graph.

@jacobtylerwalls
Copy link
Member

jacobtylerwalls commented Sep 7, 2025

I never know if the users requesting new settings have fully explored the existing ones. Can't we expect users to just adjust the max-branches setting? If we need better tooling to do that per module, we need better tooling to do that per module.

@Pierre-Sassoulas
Copy link
Member Author

I think their idea is to have max-branches behave differently for match cases because they "are arguably way less complex than any other solution." while still having the default max-branches value for other code constructs.

@jacobtylerwalls
Copy link
Member

I think we should keep checkers simple and easy to reason about until we can't. If you only update one setting and then find that your team can cheat by factoring code into or out of match case, that's a surprise.

@Pierre-Sassoulas
Copy link
Member Author

Let's vote:
Option : ❀️
No option: πŸš€

@Pierre-Sassoulas Pierre-Sassoulas added Needs decision πŸ”’ Needs a decision before implemention or rejection and removed Needs specification πŸ” Accepted as a potential improvement, and needs to specify edge cases, message names, etc. labels Sep 12, 2025
@Pierre-Sassoulas Pierre-Sassoulas added this to the 4.0.0 milestone Sep 12, 2025
@cdce8p
Copy link
Member

cdce8p commented Sep 12, 2025

Let's vote: Option : ❀️ No option: πŸš€

I lean towards no option. If we add one, I think it should only be for too-complex.

@Pierre-Sassoulas Pierre-Sassoulas added False Negative πŸ¦‹ No message is emitted but something is wrong with the code and removed Discussion πŸ€” Needs decision πŸ”’ Needs a decision before implemention or rejection labels Sep 13, 2025
Pierre-Sassoulas added a commit to Pierre-Sassoulas/pylint that referenced this pull request Sep 14, 2025
We decided against an option to permit to not count them.

Refs pylint-dev#9667
@Pierre-Sassoulas Pierre-Sassoulas changed the title Add a test case for too-complex in match case, for discussion [mccabe - too-complex] Add each match case as an edge in the graph Sep 14, 2025
Copy link

codecov bot commented Sep 14, 2025

Codecov Report

βœ… All modified and coverable lines are covered by tests.
βœ… Project coverage is 95.89%. Comparing base (c708b6a) to head (a43ce55).
⚠️ Report is 5 commits behind head on main.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #9667      +/-   ##
==========================================
- Coverage   95.90%   95.89%   -0.01%     
==========================================
  Files         177      177              
  Lines       19368    19419      +51     
==========================================
+ Hits        18574    18622      +48     
- Misses        794      797       +3     
Files with missing lines Coverage Ξ”
pylint/extensions/mccabe.py 97.47% <100.00%> (+0.20%) ⬆️

... and 10 files with indirect coverage changes

πŸš€ New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

This comment has been minimized.

We decided against an option to permit to not count them.

Refs #9667
@Pierre-Sassoulas
Copy link
Member Author

Pierre-Sassoulas commented Sep 14, 2025

I wonder if we should instead modify https://github.com/pycqa/mccabe directly so it handle match case or if we should simply drop the dependency and vendor it. It hasn't been upgraded since 2021. And it's hard to optimize performance if we don't understand what's in it (which I don't for the moment, but it's looking like adding handling for match in PathGraphingAstVisitor would simplify things). Also it's a maintenance / supply chain attack risk for a hundred line of codes. (#10551)

This comment has been minimized.

cdce8p
cdce8p previously approved these changes Sep 14, 2025
Copy link
Member

@cdce8p cdce8p left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not an expert in the mccabe code but this looks about right to me.

@Pierre-Sassoulas
Copy link
Member Author

Not an expert in the mccabe code but this looks about right to me.

Looked at the mccabe code for long enough to see that we're re-using a piece of code intended to generate dot graph, so whatever I did here it's going to be a small detail if we ever merge #10551 and want to optimize later

cdce8p
cdce8p previously approved these changes Sep 14, 2025

This comment has been minimized.

@Pierre-Sassoulas Pierre-Sassoulas enabled auto-merge (squash) September 15, 2025 14:00
Copy link
Contributor

πŸ€– Effect of this PR on checked open source code: πŸ€–

Effect on home-assistant:
The following messages are now emitted:

  1. too-complex:
    '_reduce_statistics' is too complex. The McCabe rating is 17
    https://github.com/home-assistant/core/blob/410c3df6dd9797d3a835ce4fea1ce6a92d77c409/homeassistant/components/recorder/statistics.py#L1019
  2. too-complex:
    '_get_max_mean_min_statistic_in_sub_period' is too complex. The McCabe rating is 14
    https://github.com/home-assistant/core/blob/410c3df6dd9797d3a835ce4fea1ce6a92d77c409/homeassistant/components/recorder/statistics.py#L1289
  3. too-complex:
    '_get_max_mean_min_statistic' is too complex. The McCabe rating is 11
    https://github.com/home-assistant/core/blob/410c3df6dd9797d3a835ce4fea1ce6a92d77c409/homeassistant/components/recorder/statistics.py#L1343
  4. too-complex:
    'async_setup_entry' is too complex. The McCabe rating is 11
    https://github.com/home-assistant/core/blob/410c3df6dd9797d3a835ce4fea1ce6a92d77c409/homeassistant/components/pi_hole/__init__.py#L57
  5. too-complex:
    'compile_statistics' is too complex. The McCabe rating is 40
    https://github.com/home-assistant/core/blob/410c3df6dd9797d3a835ce4fea1ce6a92d77c409/homeassistant/components/sensor/recorder.py#L455
  6. too-complex:
    '_update_from_device' is too complex. The McCabe rating is 20
    https://github.com/home-assistant/core/blob/410c3df6dd9797d3a835ce4fea1ce6a92d77c409/homeassistant/components/matter/climate.py#L258
  7. too-complex:
    '_get_media_types_from_query' is too complex. The McCabe rating is 11
    https://github.com/home-assistant/core/blob/410c3df6dd9797d3a835ce4fea1ce6a92d77c409/homeassistant/components/music_assistant/media_browser.py#L464
  8. too-complex:
    '_process_search_results' is too complex. The McCabe rating is 12
    https://github.com/home-assistant/core/blob/410c3df6dd9797d3a835ce4fea1ce6a92d77c409/homeassistant/components/music_assistant/media_browser.py#L515
  9. too-complex:
    '_event_listener' is too complex. The McCabe rating is 30
    https://github.com/home-assistant/core/blob/410c3df6dd9797d3a835ce4fea1ce6a92d77c409/homeassistant/components/home_connect/coordinator.py#L181
  10. too-complex:
    'process_update' is too complex. The McCabe rating is 22
    https://github.com/home-assistant/core/blob/410c3df6dd9797d3a835ce4fea1ce6a92d77c409/homeassistant/components/onkyo/media_player.py#L341
  11. too-complex:
    '_update_stream_source' is too complex. The McCabe rating is 14
    https://github.com/home-assistant/core/blob/410c3df6dd9797d3a835ce4fea1ce6a92d77c409/homeassistant/components/go2rtc/__init__.py#L306

The following messages are no longer emitted:

  1. too-complex:
    '_reduce_statistics' is too complex. The McCabe rating is 15
    https://github.com/home-assistant/core/blob/410c3df6dd9797d3a835ce4fea1ce6a92d77c409/homeassistant/components/recorder/statistics.py#L1019
  2. too-complex:
    'compile_statistics' is too complex. The McCabe rating is 38
    https://github.com/home-assistant/core/blob/410c3df6dd9797d3a835ce4fea1ce6a92d77c409/homeassistant/components/sensor/recorder.py#L455
  3. too-complex:
    '_update_from_device' is too complex. The McCabe rating is 12
    https://github.com/home-assistant/core/blob/410c3df6dd9797d3a835ce4fea1ce6a92d77c409/homeassistant/components/matter/climate.py#L258
  4. too-complex:
    '_event_listener' is too complex. The McCabe rating is 24
    https://github.com/home-assistant/core/blob/410c3df6dd9797d3a835ce4fea1ce6a92d77c409/homeassistant/components/home_connect/coordinator.py#L181

Effect on music21:
The following messages are now emitted:

  1. too-complex:
    'elementToMidiEventList' is too complex. The McCabe rating is 12
    https://github.com/cuthbertLab/music21/blob/501e13d0058804471cbfba4b2d848971c86e7582/music21/midi/translate.py#L1344

Effect on django:
The following messages are now emitted:

  1. too-complex:
    'normalize_choices' is too complex. The McCabe rating is 11
    https://github.com/django/django/blob/014be2f0dabb1eea4748df528edf65878dcfdecc/django/utils/choices.py#L72

Effect on pandas:
The following messages are now emitted:

  1. too-complex:
    '_split_by_backtick' is too complex. The McCabe rating is 14
    https://github.com/pandas-dev/pandas/blob/cc40732889b59d0ebd867b087691f02221e5666c/pandas/core/computation/parsing.py#L145

The following messages are no longer emitted:

  1. too-complex:
    '_split_by_backtick' is too complex. The McCabe rating is 11
    https://github.com/pandas-dev/pandas/blob/cc40732889b59d0ebd867b087691f02221e5666c/pandas/core/computation/parsing.py#L145

Effect on sentry:
The following messages are now emitted:

  1. too-complex:
    'handle_result' is too complex. The McCabe rating is 24
    https://github.com/getsentry/sentry/blob/999558bf4b621e4baaaf8ed266bcb66f6dab3106/src/sentry/uptime/consumers/results_consumer.py#L278
  2. too-complex:
    'create_uptime_detector' is too complex. The McCabe rating is 13
    https://github.com/getsentry/sentry/blob/999558bf4b621e4baaaf8ed266bcb66f6dab3106/src/sentry/uptime/subscriptions/subscriptions.py#L178
  3. too-complex:
    'convert_rule_condition_to_snuba_condition' is too complex. The McCabe rating is 15
    https://github.com/getsentry/sentry/blob/999558bf4b621e4baaaf8ed266bcb66f6dab3106/src/sentry/rules/conditions/event_frequency.py#L790
  4. too-complex:
    'as_log_message' is too complex. The McCabe rating is 33
    https://github.com/getsentry/sentry/blob/999558bf4b621e4baaaf8ed266bcb66f6dab3106/src/sentry/replays/usecases/summarize.py#L305
  5. too-complex:
    'as_trace_item_context' is too complex. The McCabe rating is 36
    https://github.com/getsentry/sentry/blob/999558bf4b621e4baaaf8ed266bcb66f6dab3106/src/sentry/replays/usecases/ingest/event_parser.py#L347
  6. too-complex:
    'literal' is too complex. The McCabe rating is 13
    https://github.com/getsentry/sentry/blob/999558bf4b621e4baaaf8ed266bcb66f6dab3106/src/sentry/replays/lib/eap/snuba_transpiler.py#L722
  7. too-complex:
    'convert_filter_to_snuba_condition' is too complex. The McCabe rating is 22
    https://github.com/getsentry/sentry/blob/999558bf4b621e4baaaf8ed266bcb66f6dab3106/src/sentry/workflow_engine/handlers/condition/event_frequency_query_handlers.py#L196
  8. too-complex:
    'filter_detectors' is too complex. The McCabe rating is 13
    https://github.com/getsentry/sentry/blob/999558bf4b621e4baaaf8ed266bcb66f6dab3106/src/sentry/workflow_engine/endpoints/organization_detector_index.py#L152
  9. too-complex:
    'filter_workflows' is too complex. The McCabe rating is 11
    https://github.com/getsentry/sentry/blob/999558bf4b621e4baaaf8ed266bcb66f6dab3106/src/sentry/workflow_engine/endpoints/organization_workflow_index.py#L95

The following messages are no longer emitted:

  1. too-complex:
    'handle_result' is too complex. The McCabe rating is 21
    https://github.com/getsentry/sentry/blob/999558bf4b621e4baaaf8ed266bcb66f6dab3106/src/sentry/uptime/consumers/results_consumer.py#L278
  2. too-complex:
    'create_uptime_detector' is too complex. The McCabe rating is 11
    https://github.com/getsentry/sentry/blob/999558bf4b621e4baaaf8ed266bcb66f6dab3106/src/sentry/uptime/subscriptions/subscriptions.py#L178
  3. too-complex:
    'as_trace_item_context' is too complex. The McCabe rating is 19
    https://github.com/getsentry/sentry/blob/999558bf4b621e4baaaf8ed266bcb66f6dab3106/src/sentry/replays/usecases/ingest/event_parser.py#L347

This comment was generated for commit a43ce55

@Pierre-Sassoulas Pierre-Sassoulas merged commit f0547a2 into main Sep 15, 2025
44 checks passed
@Pierre-Sassoulas Pierre-Sassoulas deleted the match-case-too-complex branch September 15, 2025 15:02
luketainton pushed a commit to luketainton/roboluke-tasks that referenced this pull request Oct 13, 2025
This PR contains the following updates:

| Package | Change | Age | Confidence |
|---|---|---|---|
| [pylint](https://github.com/pylint-dev/pylint) ([changelog](https://pylint.readthedocs.io/en/latest/whatsnew/3/)) | `<4.0.0,>=3.3.3` -> `<4.1.0,>=4.0.0` | [![age](https://developer.mend.io/api/mc/badges/age/pypi/pylint/4.0.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/pypi/pylint/3.3.9/4.0.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) |

---

### Release Notes

<details>
<summary>pylint-dev/pylint (pylint)</summary>

### [`v4.0.0`](https://github.com/pylint-dev/pylint/releases/tag/v4.0.0)

[Compare Source](pylint-dev/pylint@v3.3.9...v4.0.0)

- Pylint now supports Python 3.14.

- Pylint's inference engine (`astroid`) is now much more precise,
  understanding implicit booleanness and ternary expressions. (Thanks [@&#8203;zenlyj](https://github.com/zenlyj)!)

Consider this example:

```python
class Result:
    errors: dict | None = None

result = Result()
if result.errors:
    result.errors[field_key]

### inference engine understands result.errors cannot be None
### pylint no longer raises unsubscriptable-object
```

The required `astroid` version is now 4.0.0. See the [astroid changelog](https://pylint.readthedocs.io/projects/astroid/en/latest/changelog.html#what-s-new-in-astroid-4-0-0) for additional fixes, features, and performance improvements applicable to pylint.

- Handling of `invalid-name` at the module level was patchy. Now,
  module-level constants that are reassigned are treated as variables and checked
  against `--variable-rgx` rather than `--const-rgx`. Module-level lists,
  sets, and objects can pass against either regex.

Here, `LIMIT` is reassigned, so pylint only uses `--variable-rgx`:

```python
LIMIT = 500  # [invalid-name]
if sometimes:
    LIMIT = 1  # [invalid-name]
```

If this is undesired, refactor using *exclusive* assignment so that it is
evident that this assignment happens only once:

```python
if sometimes:
    LIMIT = 1
else:
    LIMIT = 500  # exclusive assignment: uses const regex, no warning
```

Lists, sets, and objects still pass against either `const-rgx` or `variable-rgx`
even if reassigned, but are no longer completely skipped:

```python
MY_LIST = []
my_list = []
My_List = []  # [invalid-name]
```

Remember to adjust the [regexes](https://pylint.readthedocs.io/en/latest/user_guide/messages/convention/invalid-name.html) and [allow lists](https://pylint.readthedocs.io/en/latest/user_guide/configuration/all-options.html#good-names) to your liking.

## Breaking Changes

- `invalid-name` now distinguishes module-level constants that are assigned only once
  from those that are reassigned and now applies `--variable-rgx` to the latter. Values
  other than literals (lists, sets, objects) can pass against either the constant or
  variable regexes (e.g. "LOGGER" or "logger" but not "LoGgEr").

  Remember that `--good-names` or `--good-names-rgxs` can be provided to explicitly
  allow good names.

  Closes [#&#8203;3585](pylint-dev/pylint#3585)

- The unused `pylintrc` argument to `PyLinter.__init__()` is deprecated
  and will be removed.

  Refs [#&#8203;6052](pylint-dev/pylint#6052)

- Commented out code blocks such as `#    bar() # TODO: remove dead code` will no longer emit `fixme`.

  Refs [#&#8203;9255](pylint-dev/pylint#9255)

- `pyreverse` `Run` was changed to no longer call `sys.exit()` in its `__init__`.
  You should now call `Run(args).run()` which will return the exit code instead.
  Having a class that always raised a `SystemExit` exception was considered a bug.

  Normal usage of pyreverse through the CLI will not be affected by this change.

  Refs [#&#8203;9689](pylint-dev/pylint#9689)

- The `suggestion-mode` option was removed, as pylint now always emits user-friendly hints instead
  of false-positive error messages. You should remove it from your conf if it's defined.

  Refs [#&#8203;9962](pylint-dev/pylint#9962)

- The `async.py` checker module has been renamed to `async_checker.py` since `async` is a Python keyword
  and cannot be imported directly. This allows for better testing and extensibility of the async checker functionality.

  Refs [#&#8203;10071](pylint-dev/pylint#10071)

- The message-id of `continue-in-finally` was changed from `E0116` to `W0136`. The warning is
  now emitted for every Python version since it will raise a syntax warning in Python 3.14.
  See [PEP 765 - Disallow return/break/continue that exit a finally block](https://peps.python.org/pep-0765/).

  Refs [#&#8203;10480](pylint-dev/pylint#10480)

- Removed support for `nmp.NaN` alias for `numpy.NaN` being recognized in ':ref:`nan-comparison`'. Use `np` or `numpy` instead.

  Refs [#&#8203;10583](pylint-dev/pylint#10583)

- Version requirement for `isort` has been bumped to >=5.0.0.
  The internal compatibility for older `isort` versions exposed via `pylint.utils.IsortDriver` has
  been removed.

  Refs [#&#8203;10637](pylint-dev/pylint#10637)

## New Features

- `comparison-of-constants` now uses the unicode from the ast instead of reformatting from
  the node's values preventing some bad formatting due to `utf-8` limitation. The message now uses
  `"` instead of `'` to better work with what the python ast returns.

  Refs [#&#8203;8736](pylint-dev/pylint#8736)

- Enhanced pyreverse to properly distinguish between UML relationship types (association, aggregation, composition) based on object ownership semantics. Type annotations without assignment are now treated as associations, parameter assignments as aggregations, and object instantiation as compositions.

  Closes [#&#8203;9045](pylint-dev/pylint#9045)
  Closes [#&#8203;9267](pylint-dev/pylint#9267)

- The `fixme` check can now search through docstrings as well as comments, by using
  `check-fixme-in-docstring = true` in the `[tool.pylint.miscellaneous]` section.

  Closes [#&#8203;9255](pylint-dev/pylint#9255)

- The `use-implicit-booleaness-not-x` checks now distinguish between comparisons
  used in boolean contexts and those that are not, enabling them to provide more accurate refactoring suggestions.

  Closes [#&#8203;9353](pylint-dev/pylint#9353)

- The verbose option now outputs the filenames of the files that have been checked.
  Previously, it only included the number of checked and skipped files.

  Closes [#&#8203;9357](pylint-dev/pylint#9357)

- colorized reporter now colorizes messages/categories that have been configured as `fail-on` in red inverse.
  This makes it easier to quickly find the errors that are causing pylint CI job failures.

  Closes [#&#8203;9898](pylint-dev/pylint#9898)

- Enhanced support for [@&#8203;property](https://github.com/property) decorator in pyreverse to correctly display return types of annotated properties when generating class diagrams.

  Closes [#&#8203;10057](pylint-dev/pylint#10057)

- Add --max-depth option to pyreverse to control diagram complexity. A depth of 0 shows only top-level packages, 1 shows one level of subpackages, etc.
  This helps manage visualization of large codebases by limiting the depth of displayed packages and classes.

  Refs [#&#8203;10077](pylint-dev/pylint#10077)

- Handle deferred evaluation of annotations in Python 3.14.

  Closes [#&#8203;10149](pylint-dev/pylint#10149)

- Enhanced pyreverse to properly detect aggregations for comprehensions (list, dict, set, generator).

  Closes [#&#8203;10236](pylint-dev/pylint#10236)

- `pyreverse`: add support for colorized output when using output format `mmd` (MermaidJS) and `html`.

  Closes [#&#8203;10242](pylint-dev/pylint#10242)

- pypy 3.11 is now officially supported.

  Refs [#&#8203;10287](pylint-dev/pylint#10287)

- Add support for Python 3.14.

  Refs [#&#8203;10467](pylint-dev/pylint#10467)

- Add naming styles for `ParamSpec` and `TypeVarTuple` that align with the `TypeVar` style.

  Refs [#&#8203;10541](pylint-dev/pylint#10541)

## New Checks

- Add `match-statements` checker and the following message:
  `bare-name-capture-pattern`.
  This will emit an error message when a name capture pattern is used in a match statement which would make the remaining patterns unreachable.
  This code is a SyntaxError at runtime.

  Closes [#&#8203;7128](pylint-dev/pylint#7128)

- Add new check `async-context-manager-with-regular-with` to detect async context managers used with regular `with` statements instead of `async with`.

  Refs [#&#8203;10408](pylint-dev/pylint#10408)

- Add `break-in-finally` warning. Using `break` inside the `finally` clause
  will raise a syntax warning in Python 3.14.
  See `PEP 765 - Disallow return/break/continue that exit a finally block <https://peps.python.org/pep-0765/>`\_.

  Refs [#&#8203;10480](pylint-dev/pylint#10480)

- Add new checks for invalid uses of class patterns in :keyword:`match`.

  - :ref:`invalid-match-args-definition` is emitted if :py:data:`object.__match_args__` isn't a tuple of strings.
  - :ref:`too-many-positional-sub-patterns` if there are more positional sub-patterns than specified in :py:data:`object.__match_args__`.
  - :ref:`multiple-class-sub-patterns` if there are multiple sub-patterns for the same attribute.

  Refs [#&#8203;10559](pylint-dev/pylint#10559)

- Add additional checks for suboptimal uses of class patterns in :keyword:`match`.

  - :ref:`match-class-bind-self` is emitted if a name is bound to `self` instead of
    using an `as` pattern.
  - :ref:`match-class-positional-attributes` is emitted if a class pattern has positional
    attributes when keywords could be used.

  Refs [#&#8203;10587](pylint-dev/pylint#10587)

- Add a `consider-math-not-float` message. `float("nan")` and `float("inf")` are slower
  than their counterpart `math.inf` and `math.nan` by a factor of 4 (notwithstanding
  the initial import of math) and they are also not well typed when using mypy.
  This check also catches typos in float calls as a side effect.

  The :ref:`pylint.extensions.code_style` need to be activated for this check to work.

  Refs [#&#8203;10621](pylint-dev/pylint#10621)

## False Positives Fixed

- Fix a false positive for `used-before-assignment` when a variable defined under
  an `if` and via a named expression (walrus operator) is used later when guarded
  under the same `if` test.

  Closes [#&#8203;10061](pylint-dev/pylint#10061)

- Fix :ref:`no-name-in-module` for members of `concurrent.futures` with Python 3.14.

  Closes [#&#8203;10632](pylint-dev/pylint#10632)

## False Negatives Fixed

- Fix false negative for `used-before-assignment` when a `TYPE_CHECKING` import is used as a type annotation prior to erroneous usage.

  Refs [#&#8203;8893](pylint-dev/pylint#8893)

- Match cases are now counted as edges in the McCabe graph and will increase the complexity accordingly.

  Refs [#&#8203;9667](pylint-dev/pylint#9667)

- Check module-level constants with type annotations for `invalid-name`.
  Remember to adjust `const-naming-style` or `const-rgx` to your liking.

  Closes [#&#8203;9770](pylint-dev/pylint#9770)

- Fix false negative where function-redefined (E0102) was not reported for functions with a leading underscore.

  Closes [#&#8203;9894](pylint-dev/pylint#9894)

- We now raise a `logging-too-few-args` for format string with no
  interpolation arguments at all (i.e. for something like `logging.debug("Awaiting process %s")`
  or `logging.debug("Awaiting process {pid}")`). Previously we did not raise for such case.

  Closes [#&#8203;9999](pylint-dev/pylint#9999)

- Fix false negative for `used-before-assignment` when a function is defined inside a `TYPE_CHECKING` guard block and used later.

  Closes [#&#8203;10028](pylint-dev/pylint#10028)

- Fix a false negative for `possibly-used-before-assignment` when a variable is conditionally defined
  and later assigned to a type-annotated variable.

  Closes [#&#8203;10421](pylint-dev/pylint#10421)

- Fix false negative for `deprecated-module` when a `__import__` method is used instead of `import` sentence.

  Refs [#&#8203;10453](pylint-dev/pylint#10453)

- Count match cases for `too-many-branches` check.

  Refs [#&#8203;10542](pylint-dev/pylint#10542)

- Fix false-negative where :ref:`unused-import` was not reported for names referenced in a preceding `global` statement.

  Refs [#&#8203;10633](pylint-dev/pylint#10633)

## Other Bug Fixes

- When displaying unicode with surrogates (or other potential `UnicodeEncodeError`),
  pylint will now display a '?' character (using `encode(encoding="utf-8", errors="replace")`)
  instead of crashing. The functional tests classes are also updated to handle this case.

  Closes [#&#8203;8736](pylint-dev/pylint#8736)

- Fixed unidiomatic-typecheck only checking left-hand side.

  Closes [#&#8203;10217](pylint-dev/pylint#10217)

- Fix a crash caused by malformed format strings when using `.format` with keyword arguments.

  Closes [#&#8203;10282](pylint-dev/pylint#10282)

- Fix false positive `inconsistent-return-statements` when using `quit()` or `exit()` functions.

  Closes [#&#8203;10508](pylint-dev/pylint#10508)

- Fix a crash in :ref:`nested-min-max` when using `builtins.min` or `builtins.max`
  instead of `min` or `max` directly.

  Closes [#&#8203;10626](pylint-dev/pylint#10626)

- Fixed a crash in :ref:`unnecessary-dict-index-lookup` when the index of an enumerated list
  was deleted inside a for loop.

  Closes [#&#8203;10627](pylint-dev/pylint#10627)

## Other Changes

- Remove support for launching pylint with Python 3.9.
  Code that supports Python 3.9 can still be linted with the `--py-version=3.9` setting.

  Refs [#&#8203;10405](pylint-dev/pylint#10405)

## Internal Changes

- Modified test framework to allow for different test output for different Python versions.

  Refs [#&#8203;10382](pylint-dev/pylint#10382)

</details>

---

### Configuration

πŸ“… **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

β™» **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.

πŸ”• **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MS4xNDYuMCIsInVwZGF0ZWRJblZlciI6IjQxLjE0Ni4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJ0eXBlL2RlcGVuZGVuY2llcyJdfQ==-->

Reviewed-on: https://git.tainton.uk/repos/roboluke/pulls/384
Co-authored-by: renovate[bot] <[email protected]>
Co-committed-by: renovate[bot] <[email protected]>
luketainton pushed a commit to luketainton/epage that referenced this pull request Oct 13, 2025
This PR contains the following updates:

| Package | Change | Age | Confidence |
|---|---|---|---|
| [pylint](https://github.com/pylint-dev/pylint) ([changelog](https://pylint.readthedocs.io/en/latest/whatsnew/3/)) | `<4.0.0,>=3.3.3` -> `<4.1.0,>=4.0.0` | [![age](https://developer.mend.io/api/mc/badges/age/pypi/pylint/4.0.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/pypi/pylint/3.3.9/4.0.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) |

---

### Release Notes

<details>
<summary>pylint-dev/pylint (pylint)</summary>

### [`v4.0.0`](https://github.com/pylint-dev/pylint/releases/tag/v4.0.0)

[Compare Source](pylint-dev/pylint@v3.3.9...v4.0.0)

- Pylint now supports Python 3.14.

- Pylint's inference engine (`astroid`) is now much more precise,
  understanding implicit booleanness and ternary expressions. (Thanks [@&#8203;zenlyj](https://github.com/zenlyj)!)

Consider this example:

```python
class Result:
    errors: dict | None = None

result = Result()
if result.errors:
    result.errors[field_key]

### inference engine understands result.errors cannot be None
### pylint no longer raises unsubscriptable-object
```

The required `astroid` version is now 4.0.0. See the [astroid changelog](https://pylint.readthedocs.io/projects/astroid/en/latest/changelog.html#what-s-new-in-astroid-4-0-0) for additional fixes, features, and performance improvements applicable to pylint.

- Handling of `invalid-name` at the module level was patchy. Now,
  module-level constants that are reassigned are treated as variables and checked
  against `--variable-rgx` rather than `--const-rgx`. Module-level lists,
  sets, and objects can pass against either regex.

Here, `LIMIT` is reassigned, so pylint only uses `--variable-rgx`:

```python
LIMIT = 500  # [invalid-name]
if sometimes:
    LIMIT = 1  # [invalid-name]
```

If this is undesired, refactor using *exclusive* assignment so that it is
evident that this assignment happens only once:

```python
if sometimes:
    LIMIT = 1
else:
    LIMIT = 500  # exclusive assignment: uses const regex, no warning
```

Lists, sets, and objects still pass against either `const-rgx` or `variable-rgx`
even if reassigned, but are no longer completely skipped:

```python
MY_LIST = []
my_list = []
My_List = []  # [invalid-name]
```

Remember to adjust the [regexes](https://pylint.readthedocs.io/en/latest/user_guide/messages/convention/invalid-name.html) and [allow lists](https://pylint.readthedocs.io/en/latest/user_guide/configuration/all-options.html#good-names) to your liking.

## Breaking Changes

- `invalid-name` now distinguishes module-level constants that are assigned only once
  from those that are reassigned and now applies `--variable-rgx` to the latter. Values
  other than literals (lists, sets, objects) can pass against either the constant or
  variable regexes (e.g. "LOGGER" or "logger" but not "LoGgEr").

  Remember that `--good-names` or `--good-names-rgxs` can be provided to explicitly
  allow good names.

  Closes [#&#8203;3585](pylint-dev/pylint#3585)

- The unused `pylintrc` argument to `PyLinter.__init__()` is deprecated
  and will be removed.

  Refs [#&#8203;6052](pylint-dev/pylint#6052)

- Commented out code blocks such as `#    bar() # TODO: remove dead code` will no longer emit `fixme`.

  Refs [#&#8203;9255](pylint-dev/pylint#9255)

- `pyreverse` `Run` was changed to no longer call `sys.exit()` in its `__init__`.
  You should now call `Run(args).run()` which will return the exit code instead.
  Having a class that always raised a `SystemExit` exception was considered a bug.

  Normal usage of pyreverse through the CLI will not be affected by this change.

  Refs [#&#8203;9689](pylint-dev/pylint#9689)

- The `suggestion-mode` option was removed, as pylint now always emits user-friendly hints instead
  of false-positive error messages. You should remove it from your conf if it's defined.

  Refs [#&#8203;9962](pylint-dev/pylint#9962)

- The `async.py` checker module has been renamed to `async_checker.py` since `async` is a Python keyword
  and cannot be imported directly. This allows for better testing and extensibility of the async checker functionality.

  Refs [#&#8203;10071](pylint-dev/pylint#10071)

- The message-id of `continue-in-finally` was changed from `E0116` to `W0136`. The warning is
  now emitted for every Python version since it will raise a syntax warning in Python 3.14.
  See [PEP 765 - Disallow return/break/continue that exit a finally block](https://peps.python.org/pep-0765/).

  Refs [#&#8203;10480](pylint-dev/pylint#10480)

- Removed support for `nmp.NaN` alias for `numpy.NaN` being recognized in ':ref:`nan-comparison`'. Use `np` or `numpy` instead.

  Refs [#&#8203;10583](pylint-dev/pylint#10583)

- Version requirement for `isort` has been bumped to >=5.0.0.
  The internal compatibility for older `isort` versions exposed via `pylint.utils.IsortDriver` has
  been removed.

  Refs [#&#8203;10637](pylint-dev/pylint#10637)

## New Features

- `comparison-of-constants` now uses the unicode from the ast instead of reformatting from
  the node's values preventing some bad formatting due to `utf-8` limitation. The message now uses
  `"` instead of `'` to better work with what the python ast returns.

  Refs [#&#8203;8736](pylint-dev/pylint#8736)

- Enhanced pyreverse to properly distinguish between UML relationship types (association, aggregation, composition) based on object ownership semantics. Type annotations without assignment are now treated as associations, parameter assignments as aggregations, and object instantiation as compositions.

  Closes [#&#8203;9045](pylint-dev/pylint#9045)
  Closes [#&#8203;9267](pylint-dev/pylint#9267)

- The `fixme` check can now search through docstrings as well as comments, by using
  `check-fixme-in-docstring = true` in the `[tool.pylint.miscellaneous]` section.

  Closes [#&#8203;9255](pylint-dev/pylint#9255)

- The `use-implicit-booleaness-not-x` checks now distinguish between comparisons
  used in boolean contexts and those that are not, enabling them to provide more accurate refactoring suggestions.

  Closes [#&#8203;9353](pylint-dev/pylint#9353)

- The verbose option now outputs the filenames of the files that have been checked.
  Previously, it only included the number of checked and skipped files.

  Closes [#&#8203;9357](pylint-dev/pylint#9357)

- colorized reporter now colorizes messages/categories that have been configured as `fail-on` in red inverse.
  This makes it easier to quickly find the errors that are causing pylint CI job failures.

  Closes [#&#8203;9898](pylint-dev/pylint#9898)

- Enhanced support for [@&#8203;property](https://github.com/property) decorator in pyreverse to correctly display return types of annotated properties when generating class diagrams.

  Closes [#&#8203;10057](pylint-dev/pylint#10057)

- Add --max-depth option to pyreverse to control diagram complexity. A depth of 0 shows only top-level packages, 1 shows one level of subpackages, etc.
  This helps manage visualization of large codebases by limiting the depth of displayed packages and classes.

  Refs [#&#8203;10077](pylint-dev/pylint#10077)

- Handle deferred evaluation of annotations in Python 3.14.

  Closes [#&#8203;10149](pylint-dev/pylint#10149)

- Enhanced pyreverse to properly detect aggregations for comprehensions (list, dict, set, generator).

  Closes [#&#8203;10236](pylint-dev/pylint#10236)

- `pyreverse`: add support for colorized output when using output format `mmd` (MermaidJS) and `html`.

  Closes [#&#8203;10242](pylint-dev/pylint#10242)

- pypy 3.11 is now officially supported.

  Refs [#&#8203;10287](pylint-dev/pylint#10287)

- Add support for Python 3.14.

  Refs [#&#8203;10467](pylint-dev/pylint#10467)

- Add naming styles for `ParamSpec` and `TypeVarTuple` that align with the `TypeVar` style.

  Refs [#&#8203;10541](pylint-dev/pylint#10541)

## New Checks

- Add `match-statements` checker and the following message:
  `bare-name-capture-pattern`.
  This will emit an error message when a name capture pattern is used in a match statement which would make the remaining patterns unreachable.
  This code is a SyntaxError at runtime.

  Closes [#&#8203;7128](pylint-dev/pylint#7128)

- Add new check `async-context-manager-with-regular-with` to detect async context managers used with regular `with` statements instead of `async with`.

  Refs [#&#8203;10408](pylint-dev/pylint#10408)

- Add `break-in-finally` warning. Using `break` inside the `finally` clause
  will raise a syntax warning in Python 3.14.
  See `PEP 765 - Disallow return/break/continue that exit a finally block <https://peps.python.org/pep-0765/>`\_.

  Refs [#&#8203;10480](pylint-dev/pylint#10480)

- Add new checks for invalid uses of class patterns in :keyword:`match`.

  - :ref:`invalid-match-args-definition` is emitted if :py:data:`object.__match_args__` isn't a tuple of strings.
  - :ref:`too-many-positional-sub-patterns` if there are more positional sub-patterns than specified in :py:data:`object.__match_args__`.
  - :ref:`multiple-class-sub-patterns` if there are multiple sub-patterns for the same attribute.

  Refs [#&#8203;10559](pylint-dev/pylint#10559)

- Add additional checks for suboptimal uses of class patterns in :keyword:`match`.

  - :ref:`match-class-bind-self` is emitted if a name is bound to `self` instead of
    using an `as` pattern.
  - :ref:`match-class-positional-attributes` is emitted if a class pattern has positional
    attributes when keywords could be used.

  Refs [#&#8203;10587](pylint-dev/pylint#10587)

- Add a `consider-math-not-float` message. `float("nan")` and `float("inf")` are slower
  than their counterpart `math.inf` and `math.nan` by a factor of 4 (notwithstanding
  the initial import of math) and they are also not well typed when using mypy.
  This check also catches typos in float calls as a side effect.

  The :ref:`pylint.extensions.code_style` need to be activated for this check to work.

  Refs [#&#8203;10621](pylint-dev/pylint#10621)

## False Positives Fixed

- Fix a false positive for `used-before-assignment` when a variable defined under
  an `if` and via a named expression (walrus operator) is used later when guarded
  under the same `if` test.

  Closes [#&#8203;10061](pylint-dev/pylint#10061)

- Fix :ref:`no-name-in-module` for members of `concurrent.futures` with Python 3.14.

  Closes [#&#8203;10632](pylint-dev/pylint#10632)

## False Negatives Fixed

- Fix false negative for `used-before-assignment` when a `TYPE_CHECKING` import is used as a type annotation prior to erroneous usage.

  Refs [#&#8203;8893](pylint-dev/pylint#8893)

- Match cases are now counted as edges in the McCabe graph and will increase the complexity accordingly.

  Refs [#&#8203;9667](pylint-dev/pylint#9667)

- Check module-level constants with type annotations for `invalid-name`.
  Remember to adjust `const-naming-style` or `const-rgx` to your liking.

  Closes [#&#8203;9770](pylint-dev/pylint#9770)

- Fix false negative where function-redefined (E0102) was not reported for functions with a leading underscore.

  Closes [#&#8203;9894](pylint-dev/pylint#9894)

- We now raise a `logging-too-few-args` for format string with no
  interpolation arguments at all (i.e. for something like `logging.debug("Awaiting process %s")`
  or `logging.debug("Awaiting process {pid}")`). Previously we did not raise for such case.

  Closes [#&#8203;9999](pylint-dev/pylint#9999)

- Fix false negative for `used-before-assignment` when a function is defined inside a `TYPE_CHECKING` guard block and used later.

  Closes [#&#8203;10028](pylint-dev/pylint#10028)

- Fix a false negative for `possibly-used-before-assignment` when a variable is conditionally defined
  and later assigned to a type-annotated variable.

  Closes [#&#8203;10421](pylint-dev/pylint#10421)

- Fix false negative for `deprecated-module` when a `__import__` method is used instead of `import` sentence.

  Refs [#&#8203;10453](pylint-dev/pylint#10453)

- Count match cases for `too-many-branches` check.

  Refs [#&#8203;10542](pylint-dev/pylint#10542)

- Fix false-negative where :ref:`unused-import` was not reported for names referenced in a preceding `global` statement.

  Refs [#&#8203;10633](pylint-dev/pylint#10633)

## Other Bug Fixes

- When displaying unicode with surrogates (or other potential `UnicodeEncodeError`),
  pylint will now display a '?' character (using `encode(encoding="utf-8", errors="replace")`)
  instead of crashing. The functional tests classes are also updated to handle this case.

  Closes [#&#8203;8736](pylint-dev/pylint#8736)

- Fixed unidiomatic-typecheck only checking left-hand side.

  Closes [#&#8203;10217](pylint-dev/pylint#10217)

- Fix a crash caused by malformed format strings when using `.format` with keyword arguments.

  Closes [#&#8203;10282](pylint-dev/pylint#10282)

- Fix false positive `inconsistent-return-statements` when using `quit()` or `exit()` functions.

  Closes [#&#8203;10508](pylint-dev/pylint#10508)

- Fix a crash in :ref:`nested-min-max` when using `builtins.min` or `builtins.max`
  instead of `min` or `max` directly.

  Closes [#&#8203;10626](pylint-dev/pylint#10626)

- Fixed a crash in :ref:`unnecessary-dict-index-lookup` when the index of an enumerated list
  was deleted inside a for loop.

  Closes [#&#8203;10627](pylint-dev/pylint#10627)

## Other Changes

- Remove support for launching pylint with Python 3.9.
  Code that supports Python 3.9 can still be linted with the `--py-version=3.9` setting.

  Refs [#&#8203;10405](pylint-dev/pylint#10405)

## Internal Changes

- Modified test framework to allow for different test output for different Python versions.

  Refs [#&#8203;10382](pylint-dev/pylint#10382)

</details>

---

### Configuration

πŸ“… **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

β™» **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.

πŸ”• **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MS4xNDYuMCIsInVwZGF0ZWRJblZlciI6IjQxLjE0Ni4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJ0eXBlL2RlcGVuZGVuY2llcyJdfQ==-->

Reviewed-on: https://git.tainton.uk/repos/epage/pulls/168
Co-authored-by: renovate[bot] <[email protected]>
Co-committed-by: renovate[bot] <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

False Negative πŸ¦‹ No message is emitted but something is wrong with the code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants