Skip to content

Commit 6fca823

Browse files
authored
Fix false positive unspecified-encoding when using kwargs (#8728)
False positives were being generated when passing arguments as kwargs to open() and other IO calls. This has been fixed by using inference whenever the argument was not found through previously existing methods (position and keyword) and kwargs are present. The confidence levels for both methods with/without inference have also been updated. Closes #8719
1 parent 893b98e commit 6fca823

File tree

6 files changed

+118
-45
lines changed

6 files changed

+118
-45
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix false positives generated when supplying arguments as ``**kwargs`` to IO calls like open().
2+
3+
Closes #8719

pylint/checkers/stdlib.py

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
from pylint import interfaces
1818
from pylint.checkers import BaseChecker, DeprecatedMixin, utils
19-
from pylint.interfaces import INFERENCE
19+
from pylint.interfaces import HIGH, INFERENCE
2020
from pylint.typing import MessageDefinitionTuple
2121

2222
if TYPE_CHECKING:
@@ -699,6 +699,7 @@ def _check_open_call(
699699
) -> None:
700700
"""Various checks for an open call."""
701701
mode_arg = None
702+
confidence = HIGH
702703
try:
703704
if open_module == "_io":
704705
mode_arg = utils.get_argument_from_call(
@@ -709,11 +710,12 @@ def _check_open_call(
709710
node, position=0, keyword="mode"
710711
)
711712
except utils.NoSuchArgumentError:
712-
pass
713+
mode_arg = utils.infer_kwarg_from_call(node, keyword="mode")
714+
if mode_arg:
715+
confidence = INFERENCE
713716

714717
if mode_arg:
715718
mode_arg = utils.safe_infer(mode_arg)
716-
717719
if (
718720
func_name in OPEN_FILES_MODE
719721
and isinstance(mode_arg, nodes.Const)
@@ -723,14 +725,15 @@ def _check_open_call(
723725
"bad-open-mode",
724726
node=node,
725727
args=mode_arg.value or str(mode_arg.value),
728+
confidence=confidence,
726729
)
727730

728731
if (
729732
not mode_arg
730733
or isinstance(mode_arg, nodes.Const)
731734
and (not mode_arg.value or "b" not in str(mode_arg.value))
732735
):
733-
encoding_arg = None
736+
confidence = HIGH
734737
try:
735738
if open_module == "pathlib":
736739
if node.func.attrname == "read_text":
@@ -750,13 +753,21 @@ def _check_open_call(
750753
node, position=3, keyword="encoding"
751754
)
752755
except utils.NoSuchArgumentError:
753-
self.add_message("unspecified-encoding", node=node)
756+
encoding_arg = utils.infer_kwarg_from_call(node, keyword="encoding")
757+
if encoding_arg:
758+
confidence = INFERENCE
759+
else:
760+
self.add_message(
761+
"unspecified-encoding", node=node, confidence=confidence
762+
)
754763

755764
if encoding_arg:
756765
encoding_arg = utils.safe_infer(encoding_arg)
757766

758767
if isinstance(encoding_arg, nodes.Const) and encoding_arg.value is None:
759-
self.add_message("unspecified-encoding", node=node)
768+
self.add_message(
769+
"unspecified-encoding", node=node, confidence=confidence
770+
)
760771

761772
def _check_env_function(self, node: nodes.Call, infer: nodes.FunctionDef) -> None:
762773
env_name_kwarg = "key"

pylint/checkers/utils.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -736,6 +736,25 @@ def get_argument_from_call(
736736
raise NoSuchArgumentError
737737

738738

739+
def infer_kwarg_from_call(call_node: nodes.Call, keyword: str) -> nodes.Name | None:
740+
"""Returns the specified argument from a function's kwargs.
741+
742+
:param nodes.Call call_node: Node representing a function call to check.
743+
:param str keyword: Name of the argument to be extracted.
744+
745+
:returns: The node representing the argument, None if the argument is not found.
746+
:rtype: nodes.Name
747+
"""
748+
for arg in call_node.kwargs:
749+
inferred = safe_infer(arg.value)
750+
if isinstance(inferred, nodes.Dict):
751+
for item in inferred.items:
752+
if item[0].value == keyword:
753+
return item[1]
754+
755+
return None
756+
757+
739758
def inherit_from_std_ex(node: nodes.NodeNG | astroid.Instance) -> bool:
740759
"""Return whether the given class node is subclass of
741760
exceptions.Exception.

tests/functional/b/bad_open_mode.txt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
bad-open-mode:12:0:12:35::"""rwx"" is not a valid mode for open.":UNDEFINED
2-
bad-open-mode:13:0:13:34::"""rr"" is not a valid mode for open.":UNDEFINED
3-
bad-open-mode:14:0:14:33::"""+"" is not a valid mode for open.":UNDEFINED
4-
bad-open-mode:15:0:15:34::"""xw"" is not a valid mode for open.":UNDEFINED
5-
bad-open-mode:21:0:21:34::"""Ua"" is not a valid mode for open.":UNDEFINED
6-
bad-open-mode:22:0:22:36::"""Ur++"" is not a valid mode for open.":UNDEFINED
1+
bad-open-mode:12:0:12:35::"""rwx"" is not a valid mode for open.":HIGH
2+
bad-open-mode:13:0:13:34::"""rr"" is not a valid mode for open.":HIGH
3+
bad-open-mode:14:0:14:33::"""+"" is not a valid mode for open.":HIGH
4+
bad-open-mode:15:0:15:34::"""xw"" is not a valid mode for open.":HIGH
5+
bad-open-mode:21:0:21:34::"""Ua"" is not a valid mode for open.":HIGH
6+
bad-open-mode:22:0:22:36::"""Ur++"" is not a valid mode for open.":HIGH

tests/functional/u/unspecified_encoding_py38.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,3 +162,35 @@ class IOArgs:
162162

163163
# Test for crash reported in https://github.com/pylint-dev/pylint/issues/6414
164164
open('foo', mode=2) # [bad-open-mode, unspecified-encoding]
165+
166+
# Infer kwargs
167+
KWARGS = {"mode": "rb"}
168+
open(FILENAME, **KWARGS)
169+
170+
KWARGS = {"mode": "w", "encoding": "utf-8"}
171+
open(FILENAME, **KWARGS)
172+
io.open(FILENAME, **KWARGS)
173+
Path(FILENAME).open(**KWARGS)
174+
175+
KWARGS = {"mode": 5}
176+
open(FILENAME, **KWARGS) # [bad-open-mode, unspecified-encoding]
177+
io.open(FILENAME, **KWARGS) # [bad-open-mode, unspecified-encoding]
178+
179+
KWARGS = {"mode": "wt", "encoding": None}
180+
with open(FILENAME, **KWARGS) as fd: # [unspecified-encoding]
181+
pass
182+
183+
Path(FILENAME).open(**KWARGS) # [unspecified-encoding]
184+
185+
KWARGS = {"encoding": None}
186+
Path(FILENAME).write_text("hello", **KWARGS) # [unspecified-encoding]
187+
188+
KWARGS = {"encoding": "utf-8"}
189+
Path(FILENAME).write_text("goodbye", **KWARGS)
190+
191+
# No one does it this way, but it is a possibility
192+
KWARGS = {"encoding": None}
193+
Path(FILENAME).read_text(**KWARGS) # [unspecified-encoding]
194+
195+
KWARGS = {"encoding": "utf-8"}
196+
Path(FILENAME).read_text(**KWARGS)
Lines changed: 41 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,41 @@
1-
unspecified-encoding:13:0:13:14::Using open without explicitly specifying an encoding:UNDEFINED
2-
unspecified-encoding:14:0:14:20::Using open without explicitly specifying an encoding:UNDEFINED
3-
unspecified-encoding:15:0:15:20::Using open without explicitly specifying an encoding:UNDEFINED
4-
unspecified-encoding:16:0:16:34::Using open without explicitly specifying an encoding:UNDEFINED
5-
unspecified-encoding:17:0:17:19::Using open without explicitly specifying an encoding:UNDEFINED
6-
unspecified-encoding:26:5:26:19::Using open without explicitly specifying an encoding:UNDEFINED
7-
unspecified-encoding:29:5:29:34::Using open without explicitly specifying an encoding:UNDEFINED
8-
unspecified-encoding:33:5:33:45::Using open without explicitly specifying an encoding:UNDEFINED
9-
unspecified-encoding:38:0:38:17::Using open without explicitly specifying an encoding:UNDEFINED
10-
unspecified-encoding:39:0:39:23::Using open without explicitly specifying an encoding:UNDEFINED
11-
unspecified-encoding:40:0:40:23::Using open without explicitly specifying an encoding:UNDEFINED
12-
unspecified-encoding:41:0:41:37::Using open without explicitly specifying an encoding:UNDEFINED
13-
unspecified-encoding:50:5:50:22::Using open without explicitly specifying an encoding:UNDEFINED
14-
unspecified-encoding:53:5:53:37::Using open without explicitly specifying an encoding:UNDEFINED
15-
unspecified-encoding:57:5:57:48::Using open without explicitly specifying an encoding:UNDEFINED
16-
unspecified-encoding:66:0:66:26::Using open without explicitly specifying an encoding:UNDEFINED
17-
unspecified-encoding:67:0:67:39::Using open without explicitly specifying an encoding:UNDEFINED
18-
unspecified-encoding:68:0:68:50::Using open without explicitly specifying an encoding:UNDEFINED
19-
unspecified-encoding:75:0:75:35::Using open without explicitly specifying an encoding:UNDEFINED
20-
unspecified-encoding:76:0:76:50::Using open without explicitly specifying an encoding:UNDEFINED
21-
unspecified-encoding:77:0:77:61::Using open without explicitly specifying an encoding:UNDEFINED
22-
unspecified-encoding:81:0:81:21::Using open without explicitly specifying an encoding:UNDEFINED
23-
unspecified-encoding:82:0:82:25::Using open without explicitly specifying an encoding:UNDEFINED
24-
unspecified-encoding:83:0:83:25::Using open without explicitly specifying an encoding:UNDEFINED
25-
unspecified-encoding:84:0:84:39::Using open without explicitly specifying an encoding:UNDEFINED
26-
unspecified-encoding:149:0:149:23::Using open without explicitly specifying an encoding:UNDEFINED
27-
unspecified-encoding:152:0:152:28::Using open without explicitly specifying an encoding:UNDEFINED
28-
unspecified-encoding:155:0:155:26::Using open without explicitly specifying an encoding:UNDEFINED
29-
unspecified-encoding:158:0:158:35::Using open without explicitly specifying an encoding:UNDEFINED
30-
bad-open-mode:161:0:161:25::"""None"" is not a valid mode for open.":UNDEFINED
31-
unspecified-encoding:161:0:161:25::Using open without explicitly specifying an encoding:UNDEFINED
32-
bad-open-mode:164:0:164:19::"""2"" is not a valid mode for open.":UNDEFINED
33-
unspecified-encoding:164:0:164:19::Using open without explicitly specifying an encoding:UNDEFINED
1+
unspecified-encoding:13:0:13:14::Using open without explicitly specifying an encoding:HIGH
2+
unspecified-encoding:14:0:14:20::Using open without explicitly specifying an encoding:HIGH
3+
unspecified-encoding:15:0:15:20::Using open without explicitly specifying an encoding:HIGH
4+
unspecified-encoding:16:0:16:34::Using open without explicitly specifying an encoding:HIGH
5+
unspecified-encoding:17:0:17:19::Using open without explicitly specifying an encoding:HIGH
6+
unspecified-encoding:26:5:26:19::Using open without explicitly specifying an encoding:HIGH
7+
unspecified-encoding:29:5:29:34::Using open without explicitly specifying an encoding:HIGH
8+
unspecified-encoding:33:5:33:45::Using open without explicitly specifying an encoding:HIGH
9+
unspecified-encoding:38:0:38:17::Using open without explicitly specifying an encoding:HIGH
10+
unspecified-encoding:39:0:39:23::Using open without explicitly specifying an encoding:HIGH
11+
unspecified-encoding:40:0:40:23::Using open without explicitly specifying an encoding:HIGH
12+
unspecified-encoding:41:0:41:37::Using open without explicitly specifying an encoding:HIGH
13+
unspecified-encoding:50:5:50:22::Using open without explicitly specifying an encoding:HIGH
14+
unspecified-encoding:53:5:53:37::Using open without explicitly specifying an encoding:HIGH
15+
unspecified-encoding:57:5:57:48::Using open without explicitly specifying an encoding:HIGH
16+
unspecified-encoding:66:0:66:26::Using open without explicitly specifying an encoding:HIGH
17+
unspecified-encoding:67:0:67:39::Using open without explicitly specifying an encoding:HIGH
18+
unspecified-encoding:68:0:68:50::Using open without explicitly specifying an encoding:HIGH
19+
unspecified-encoding:75:0:75:35::Using open without explicitly specifying an encoding:HIGH
20+
unspecified-encoding:76:0:76:50::Using open without explicitly specifying an encoding:HIGH
21+
unspecified-encoding:77:0:77:61::Using open without explicitly specifying an encoding:HIGH
22+
unspecified-encoding:81:0:81:21::Using open without explicitly specifying an encoding:HIGH
23+
unspecified-encoding:82:0:82:25::Using open without explicitly specifying an encoding:HIGH
24+
unspecified-encoding:83:0:83:25::Using open without explicitly specifying an encoding:HIGH
25+
unspecified-encoding:84:0:84:39::Using open without explicitly specifying an encoding:HIGH
26+
unspecified-encoding:149:0:149:23::Using open without explicitly specifying an encoding:HIGH
27+
unspecified-encoding:152:0:152:28::Using open without explicitly specifying an encoding:HIGH
28+
unspecified-encoding:155:0:155:26::Using open without explicitly specifying an encoding:HIGH
29+
unspecified-encoding:158:0:158:35::Using open without explicitly specifying an encoding:HIGH
30+
bad-open-mode:161:0:161:25::"""None"" is not a valid mode for open.":HIGH
31+
unspecified-encoding:161:0:161:25::Using open without explicitly specifying an encoding:HIGH
32+
bad-open-mode:164:0:164:19::"""2"" is not a valid mode for open.":HIGH
33+
unspecified-encoding:164:0:164:19::Using open without explicitly specifying an encoding:HIGH
34+
bad-open-mode:176:0:176:24::"""5"" is not a valid mode for open.":INFERENCE
35+
unspecified-encoding:176:0:176:24::Using open without explicitly specifying an encoding:HIGH
36+
bad-open-mode:177:0:177:27::"""5"" is not a valid mode for open.":INFERENCE
37+
unspecified-encoding:177:0:177:27::Using open without explicitly specifying an encoding:HIGH
38+
unspecified-encoding:180:5:180:29::Using open without explicitly specifying an encoding:INFERENCE
39+
unspecified-encoding:183:0:183:29::Using open without explicitly specifying an encoding:INFERENCE
40+
unspecified-encoding:186:0:186:44::Using open without explicitly specifying an encoding:INFERENCE
41+
unspecified-encoding:193:0:193:34::Using open without explicitly specifying an encoding:INFERENCE

0 commit comments

Comments
 (0)