summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarc Mueller <30130371+cdce8p@users.noreply.github.com>2021-04-12 09:12:33 +0200
committerGitHub <noreply@github.com>2021-04-12 09:12:33 +0200
commita9e31583ddff80fb1226d3f4f07c58398fca2619 (patch)
treebe74f25f062e2dc60344839eb05bfdff7629aba8
parent38d15c98316359c7b0b190f2245a3b2e2bf62109 (diff)
downloadpylint-git-a9e31583ddff80fb1226d3f4f07c58398fca2619.tar.gz
Improve check for invalid PEP 585 syntax inside functions (#4340)
* Improve check for invalid PEP 585 syntax inside functions * Improve check for invalid PEP 585 syntax as default function arguments
-rw-r--r--ChangeLog5
-rw-r--r--pylint/checkers/utils.py35
-rw-r--r--tests/functional/p/postponed_evaluation_pep585.py15
-rw-r--r--tests/functional/p/postponed_evaluation_pep585.txt4
-rw-r--r--tests/functional/p/postponed_evaluation_pep585_error.py15
-rw-r--r--tests/functional/p/postponed_evaluation_pep585_error.txt6
-rw-r--r--tests/functional/p/postponed_evaluation_pep585_py39.py15
7 files changed, 80 insertions, 15 deletions
diff --git a/ChangeLog b/ChangeLog
index 025b92794..9019de244 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -90,6 +90,11 @@ Release date: Undefined
* Fix issue that caused emacs pylint to fail when used with tramp
+* Improve check for invalid PEP 585 syntax inside functions
+ if postponed evaluation of type annotations is enabled
+
+* Improve check for invalid PEP 585 syntax as default function arguments
+
What's New in Pylint 2.7.4?
===========================
diff --git a/pylint/checkers/utils.py b/pylint/checkers/utils.py
index 2e1c659ed..4fefef61c 100644
--- a/pylint/checkers/utils.py
+++ b/pylint/checkers/utils.py
@@ -1341,23 +1341,34 @@ def is_class_subscriptable_pep585_with_postponed_evaluation_enabled(
"""Check if class is subscriptable with PEP 585 and
postponed evaluation enabled.
"""
- if (
- not is_postponed_evaluation_enabled(node)
- or value.qname() not in SUBSCRIPTABLE_CLASSES_PEP585
- ):
- return False
+ return (
+ is_postponed_evaluation_enabled(node)
+ and value.qname() in SUBSCRIPTABLE_CLASSES_PEP585
+ and is_node_in_type_annotation_context(node)
+ )
+
- parent_node = node.parent
+def is_node_in_type_annotation_context(node: astroid.node_classes.NodeNG) -> bool:
+ """Check if node is in type annotation context.
+
+ Check for 'AnnAssign', function 'Arguments',
+ or part of function return type anntation.
+ """
+ # pylint: disable=too-many-boolean-expressions
+ current_node, parent_node = node, node.parent
while True:
- # Check if any parent node matches condition
- if isinstance(
- parent_node, (astroid.AnnAssign, astroid.Arguments, astroid.FunctionDef)
+ if (
+ isinstance(parent_node, astroid.AnnAssign)
+ and parent_node.annotation == current_node
+ or isinstance(parent_node, astroid.Arguments)
+ and current_node in parent_node.annotations
+ or isinstance(parent_node, astroid.FunctionDef)
+ and parent_node.returns == current_node
):
- break
- parent_node = parent_node.parent
+ return True
+ current_node, parent_node = parent_node, parent_node.parent
if isinstance(parent_node, astroid.Module):
return False
- return True
def is_subclass_of(child: astroid.ClassDef, parent: astroid.ClassDef) -> bool:
diff --git a/tests/functional/p/postponed_evaluation_pep585.py b/tests/functional/p/postponed_evaluation_pep585.py
index e007a4a5f..2a2f30ccf 100644
--- a/tests/functional/p/postponed_evaluation_pep585.py
+++ b/tests/functional/p/postponed_evaluation_pep585.py
@@ -3,7 +3,7 @@
This check requires Python 3.7 or 3.8!
Testing with 3.8 only, to support TypedDict.
"""
-# pylint: disable=missing-docstring,unused-argument,unused-import,too-few-public-methods,invalid-name,inherit-non-class,unsupported-binary-operation,wrong-import-position,ungrouped-imports
+# pylint: disable=missing-docstring,unused-argument,unused-import,too-few-public-methods,invalid-name,inherit-non-class,unsupported-binary-operation,wrong-import-position,ungrouped-imports,unused-variable
from __future__ import annotations
import collections
import dataclasses
@@ -115,3 +115,16 @@ var15: collections.Counter[int]
var16: collections.abc.Iterable[int]
var17: contextlib.AbstractContextManager[int]
var18: re.Pattern[str]
+
+
+def func3():
+ AliasInvalid2 = list[int] # [unsubscriptable-object]
+ cast_variable2 = [1, 2, 3]
+ cast_variable2 = typing.cast(list[int], cast_variable2) # [unsubscriptable-object]
+ var19: list[int]
+
+def func4(arg=list[int]): # [unsubscriptable-object]
+ pass
+
+def func5(arg1: list[int], arg2=set[int]): # [unsubscriptable-object]
+ pass
diff --git a/tests/functional/p/postponed_evaluation_pep585.txt b/tests/functional/p/postponed_evaluation_pep585.txt
index 12c524491..2d595896c 100644
--- a/tests/functional/p/postponed_evaluation_pep585.txt
+++ b/tests/functional/p/postponed_evaluation_pep585.txt
@@ -14,3 +14,7 @@ unsubscriptable-object:100:15::Value 'list' is unsubscriptable
unsubscriptable-object:101:9::Value 'list' is unsubscriptable
unsubscriptable-object:101:14::Value 'list' is unsubscriptable
unsubscriptable-object:111:7::Value 'OrderedDict' is unsubscriptable
+unsubscriptable-object:121:20:func3:Value 'list' is unsubscriptable
+unsubscriptable-object:123:33:func3:Value 'list' is unsubscriptable
+unsubscriptable-object:126:14:func4:Value 'list' is unsubscriptable
+unsubscriptable-object:129:32:func5:Value 'set' is unsubscriptable
diff --git a/tests/functional/p/postponed_evaluation_pep585_error.py b/tests/functional/p/postponed_evaluation_pep585_error.py
index bcc41def8..7eaf9b05c 100644
--- a/tests/functional/p/postponed_evaluation_pep585_error.py
+++ b/tests/functional/p/postponed_evaluation_pep585_error.py
@@ -3,7 +3,7 @@
This check requires Python 3.7 or Python 3.8!
Testing with 3.8 only, to support TypedDict.
"""
-# pylint: disable=missing-docstring,unused-argument,unused-import,too-few-public-methods,invalid-name,inherit-non-class,unsupported-binary-operation
+# pylint: disable=missing-docstring,unused-argument,unused-import,too-few-public-methods,invalid-name,inherit-non-class,unsupported-binary-operation,unused-variable
import collections
import dataclasses
import typing
@@ -97,3 +97,16 @@ Alias3 = Union[Union[list[int], int]] # [unsubscriptable-object]
Alias5 = Dict[str, list[int]] # [unsubscriptable-object]
Alias6 = int | list[int] # [unsubscriptable-object]
Alias7 = list[list[int]] # [unsubscriptable-object,unsubscriptable-object]
+
+
+def func3():
+ AliasInvalid2 = list[int] # [unsubscriptable-object]
+ cast_variable2 = [1, 2, 3]
+ cast_variable2 = typing.cast(list[int], cast_variable2) # [unsubscriptable-object]
+ var12: list[int] # [unsubscriptable-object]
+
+def func4(var=list[int]): # [unsubscriptable-object]
+ pass
+
+def func5(arg1: list[int], arg2=set[int]): # [unsubscriptable-object,unsubscriptable-object]
+ pass
diff --git a/tests/functional/p/postponed_evaluation_pep585_error.txt b/tests/functional/p/postponed_evaluation_pep585_error.txt
index 31dd1f465..d3697dc67 100644
--- a/tests/functional/p/postponed_evaluation_pep585_error.txt
+++ b/tests/functional/p/postponed_evaluation_pep585_error.txt
@@ -37,3 +37,9 @@ unsubscriptable-object:97:19::Value 'list' is unsubscriptable
unsubscriptable-object:98:15::Value 'list' is unsubscriptable
unsubscriptable-object:99:9::Value 'list' is unsubscriptable
unsubscriptable-object:99:14::Value 'list' is unsubscriptable
+unsubscriptable-object:103:20:func3:Value 'list' is unsubscriptable
+unsubscriptable-object:105:33:func3:Value 'list' is unsubscriptable
+unsubscriptable-object:106:11:func3:Value 'list' is unsubscriptable
+unsubscriptable-object:108:14:func4:Value 'list' is unsubscriptable
+unsubscriptable-object:111:16:func5:Value 'list' is unsubscriptable
+unsubscriptable-object:111:32:func5:Value 'set' is unsubscriptable
diff --git a/tests/functional/p/postponed_evaluation_pep585_py39.py b/tests/functional/p/postponed_evaluation_pep585_py39.py
index 387c2273c..75c109a0d 100644
--- a/tests/functional/p/postponed_evaluation_pep585_py39.py
+++ b/tests/functional/p/postponed_evaluation_pep585_py39.py
@@ -1,5 +1,5 @@
"""Test PEP 585 works as expected, starting with Python 3.9"""
-# pylint: disable=missing-docstring,unused-argument,unused-import,too-few-public-methods,invalid-name,inherit-non-class,unsupported-binary-operation,wrong-import-position,ungrouped-imports
+# pylint: disable=missing-docstring,unused-argument,unused-import,too-few-public-methods,invalid-name,inherit-non-class,unsupported-binary-operation,wrong-import-position,ungrouped-imports,unused-variable
import collections
import dataclasses
import typing
@@ -110,3 +110,16 @@ var15: collections.Counter[int]
var16: collections.abc.Iterable[int]
var17: contextlib.AbstractContextManager[int]
var18: re.Pattern[str]
+
+
+def func3():
+ AliasInvalid2 = list[int]
+ cast_variable2 = [1, 2, 3]
+ cast_variable2 = typing.cast(list[int], cast_variable2)
+ var19: list[int]
+
+def func4(var=list[int]):
+ pass
+
+def func5(arg1: list[int], arg2=set[int]):
+ pass