Skip to content

Always use the suggestion mode for no-member / c-extension-no-member #9962

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ on:
- "maintenance/**"

env:
CACHE_VERSION: 4
CACHE_VERSION: 5
KEY_PREFIX: venv

permissions:
Expand Down
9 changes: 0 additions & 9 deletions doc/user_guide/configuration/all-options.rst
Original file line number Diff line number Diff line change
Expand Up @@ -209,13 +209,6 @@ Standard Checkers
**Default:** ``()``


--suggestion-mode
"""""""""""""""""
*When enabled, pylint would attempt to guess common misconfiguration and emit user-friendly hints instead of false-positive error messages.*

**Default:** ``True``


--unsafe-load-any-extension
"""""""""""""""""""""""""""
*Allow loading of arbitrary C extensions. Extensions are imported into the active Python interpreter and may run arbitrary code.*
Expand Down Expand Up @@ -290,8 +283,6 @@ Standard Checkers

source-roots = []

suggestion-mode = true

unsafe-load-any-extension = false


Expand Down
4 changes: 4 additions & 0 deletions doc/whatsnew/fragments/9962.breaking
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
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 #9962
4 changes: 0 additions & 4 deletions examples/pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,6 @@ recursive=no
# source root.
source-roots=

# When enabled, pylint would attempt to guess common misconfiguration and emit
# user-friendly hints instead of false-positive error messages.
suggestion-mode=yes

# Allow loading of arbitrary C extensions. Extensions are imported into the
# active Python interpreter and may run arbitrary code.
unsafe-load-any-extension=no
Expand Down
4 changes: 0 additions & 4 deletions examples/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,6 @@ py-version = "3.12"
# source root.
# source-roots =

# When enabled, pylint would attempt to guess common misconfiguration and emit
# user-friendly hints instead of false-positive error messages.
suggestion-mode = true

# Allow loading of arbitrary C extensions. Extensions are imported into the
# active Python interpreter and may run arbitrary code.
# unsafe-load-any-extension =
Expand Down
37 changes: 37 additions & 0 deletions pylint/checkers/clear_lru_cache.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
# For details: https://github.com/pylint-dev/pylint/blob/main/LICENSE
# Copyright (c) https://github.com/pylint-dev/pylint/blob/main/CONTRIBUTORS.txt

from __future__ import annotations

from typing import TYPE_CHECKING, Any

from pylint.checkers.typecheck import _similar_names
from pylint.checkers.utils import (
class_is_abstract,
in_for_else_branch,
infer_all,
is_overload_stub,
overridden_method,
safe_infer,
unimplemented_abstract_methods,
)

if TYPE_CHECKING:
from functools import _lru_cache_wrapper


def clear_lru_caches() -> None:
"""Clear caches holding references to AST nodes."""
caches_holding_node_references: list[_lru_cache_wrapper[Any]] = [
class_is_abstract,
in_for_else_branch,
infer_all,
is_overload_stub,
overridden_method,
unimplemented_abstract_methods,
safe_infer,
_similar_names,
]
for lru in caches_holding_node_references:
lru.cache_clear()
59 changes: 18 additions & 41 deletions pylint/checkers/typecheck.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import shlex
import sys
from collections.abc import Callable, Iterable
from functools import cached_property, singledispatch
from functools import cached_property, lru_cache, singledispatch
from re import Pattern
from typing import TYPE_CHECKING, Any, Literal, Union

Expand Down Expand Up @@ -172,6 +172,7 @@ def _string_distance(seq1: str, seq2: str, seq1_length: int, seq2_length: int) -
return row[seq2_length - 1]


@lru_cache(maxsize=256)
Copy link
Member

Choose a reason for hiding this comment

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

Can we add this to clear_lru_caches()?

Copy link
Member Author

Choose a reason for hiding this comment

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

This created a circular import between typecheck and utils so I created a new file.

def _similar_names(
owner: SuccessfulInferenceResult,
attrname: str | None,
Expand Down Expand Up @@ -214,26 +215,6 @@ def _similar_names(
return sorted(picked)


def _missing_member_hint(
owner: SuccessfulInferenceResult,
attrname: str | None,
distance_threshold: int,
max_choices: int,
) -> str:
names = _similar_names(owner, attrname, distance_threshold, max_choices)
if not names:
# No similar name.
return ""

names = [repr(name) for name in names]
if len(names) == 1:
names_hint = ", ".join(names)
else:
names_hint = f"one of {', '.join(names[:-1])} or {names[-1]}"

return f"; maybe {names_hint}?"


MSGS: dict[str, MessageDefinitionTuple] = {
"E1101": (
"%s %r has no %r member%s",
Expand Down Expand Up @@ -997,10 +978,6 @@ def open(self) -> None:
self._py310_plus = py_version >= (3, 10)
self._mixin_class_rgx = self.linter.config.mixin_class_rgx

@cached_property
def _suggestion_mode(self) -> bool:
return self.linter.config.suggestion_mode # type: ignore[no-any-return]

@cached_property
def _compiled_generated_members(self) -> tuple[Pattern[str], ...]:
# do this lazily since config not fully initialized in __init__
Expand Down Expand Up @@ -1211,24 +1188,24 @@ def _get_nomember_msgid_hint(
node: nodes.Attribute | nodes.AssignAttr | nodes.DelAttr,
owner: SuccessfulInferenceResult,
) -> tuple[Literal["c-extension-no-member", "no-member"], str]:
suggestions_are_possible = self._suggestion_mode and isinstance(
owner, nodes.Module
if _is_c_extension(owner):
return "c-extension-no-member", ""
if not self.linter.config.missing_member_hint:
return "no-member", ""
names = _similar_names(
owner,
node.attrname,
self.linter.config.missing_member_hint_distance,
self.linter.config.missing_member_max_choices,
)
if suggestions_are_possible and _is_c_extension(owner):
msg = "c-extension-no-member"
hint = ""
if not names:
return "no-member", ""
names = [repr(name) for name in names]
if len(names) == 1:
names_hint = names[0]
else:
msg = "no-member"
if self.linter.config.missing_member_hint:
hint = _missing_member_hint(
owner,
node.attrname,
self.linter.config.missing_member_hint_distance,
self.linter.config.missing_member_max_choices,
)
else:
hint = ""
return msg, hint # type: ignore[return-value]
names_hint = f"one of {', '.join(names[:-1])} or {names[-1]}"
return "no-member", f"; maybe {names_hint}?"

@only_required_for_messages(
"assignment-from-no-return",
Expand Down
18 changes: 1 addition & 17 deletions pylint/checkers/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from collections.abc import Callable, Iterable, Iterator
from functools import lru_cache, partial
from re import Match
from typing import TYPE_CHECKING, Any, TypeVar
from typing import TYPE_CHECKING, TypeVar

import astroid.objects
from astroid import TooManyLevelsError, nodes, util
Expand All @@ -28,7 +28,6 @@
from pylint.constants import TYPING_NEVER, TYPING_NORETURN

if TYPE_CHECKING:
from functools import _lru_cache_wrapper

from pylint.checkers import BaseChecker

Expand Down Expand Up @@ -2327,21 +2326,6 @@ def overridden_method(
return None # pragma: no cover


def clear_lru_caches() -> None:
"""Clear caches holding references to AST nodes."""
caches_holding_node_references: list[_lru_cache_wrapper[Any]] = [
class_is_abstract,
in_for_else_branch,
infer_all,
is_overload_stub,
overridden_method,
unimplemented_abstract_methods,
safe_infer,
]
for lru in caches_holding_node_references:
lru.cache_clear()


def is_enum_member(node: nodes.AssignName) -> bool:
"""Return `True` if `node` is an Enum member (is an item of the
`__members__` container).
Expand Down
13 changes: 0 additions & 13 deletions pylint/lint/base_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -306,19 +306,6 @@ def _make_linter_options(linter: PyLinter) -> Options:
),
},
),
(
"suggestion-mode",
{
"type": "yn",
"metavar": "<y or n>",
"default": True,
"help": (
"When enabled, pylint would attempt to guess common "
"misconfiguration and emit user-friendly hints instead "
"of false-positive error messages."
),
},
),
(
"exit-zero",
{
Expand Down
2 changes: 1 addition & 1 deletion pylint/lint/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from typing import ClassVar

from pylint import config
from pylint.checkers.utils import clear_lru_caches
from pylint.checkers.clear_lru_cache import clear_lru_caches
from pylint.config._pylint_config import (
_handle_pylint_config_commands,
_register_generate_config_options,
Expand Down
1 change: 0 additions & 1 deletion pylint/utils/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@

# These are types used to overload get_global_option() and refer to the options type
GLOBAL_OPTION_BOOL = Literal[
"suggestion-mode",
"analyse-fallback-blocks",
"allow-global-unused-variables",
"prefer-stubs",
Expand Down
4 changes: 0 additions & 4 deletions pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,6 @@ load-plugins=
# number of processors available to use.
jobs=1

# When enabled, pylint would attempt to guess common misconfiguration and emit
# user-friendly hints instead of false-positive error messages.
suggestion-mode=yes

# Allow loading of arbitrary C extensions. Extensions are imported into the
# active Python interpreter and may run arbitrary code.
unsafe-load-any-extension=no
Expand Down
25 changes: 1 addition & 24 deletions tests/checkers/unittest_typecheck.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from pylint.checkers import typecheck
from pylint.interfaces import INFERENCE, UNDEFINED
from pylint.testutils import CheckerTestCase, MessageTest, set_config
from pylint.testutils import CheckerTestCase, MessageTest

try:
from coverage import tracer as _
Expand All @@ -27,29 +27,6 @@ class TestTypeChecker(CheckerTestCase):

CHECKER_CLASS = typecheck.TypeChecker

@set_config(suggestion_mode=False)
@needs_c_extension
def test_nomember_on_c_extension_error_msg(self) -> None:
node = astroid.extract_node(
"""
from coverage import tracer
tracer.CTracer #@
"""
)
message = MessageTest(
"no-member",
node=node,
args=("Module", "coverage.tracer", "CTracer", ""),
confidence=INFERENCE,
line=3,
col_offset=0,
end_line=3,
end_col_offset=14,
)
with self.assertAddsMessages(message):
self.checker.visit_attribute(node)

@set_config(suggestion_mode=True)
@needs_c_extension
def test_nomember_on_c_extension_info_msg(self) -> None:
node = astroid.extract_node(
Expand Down
Loading