Skip to content

Commit

Permalink
Don't emit broken typing errors inside TYPE_CHECKING blocks (#5984)
Browse files Browse the repository at this point in the history
  • Loading branch information
cdce8p authored and Pierre-Sassoulas committed Mar 26, 2022
1 parent 4a92de7 commit ef2e176
Show file tree
Hide file tree
Showing 10 changed files with 55 additions and 24 deletions.
3 changes: 3 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ Release date: TBA

Closes #5187

* Don't emit ``broken-noreturn`` and ``broken-collections-callable`` errors
inside ``if TYPE_CHECKING`` blocks.


What's New in Pylint 2.13.0?
============================
Expand Down
11 changes: 11 additions & 0 deletions pylint/checkers/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
from astroid import TooManyLevelsError, nodes
from astroid.context import InferenceContext

from pylint.constants import TYPING_TYPE_CHECKS_GUARDS

COMP_NODE_TYPES = (
nodes.ListComp,
nodes.SetComp,
Expand Down Expand Up @@ -1708,3 +1710,12 @@ def get_node_first_ancestor_of_type_and_its_child(
return (ancestor, child)
child = ancestor
return None, None


def in_type_checking_block(node: nodes.NodeNG) -> bool:
"""Check if a node is guarded by a TYPE_CHECKS guard."""
return any(
isinstance(ancestor, nodes.If)
and ancestor.test.as_string() in TYPING_TYPE_CHECKS_GUARDS
for ancestor in node.node_ancestors()
)
21 changes: 7 additions & 14 deletions pylint/checkers/variables.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,11 @@
from astroid import nodes

from pylint.checkers import BaseChecker, utils
from pylint.checkers.utils import is_postponed_evaluation_enabled
from pylint.constants import PY39_PLUS
from pylint.checkers.utils import (
in_type_checking_block,
is_postponed_evaluation_enabled,
)
from pylint.constants import PY39_PLUS, TYPING_TYPE_CHECKS_GUARDS
from pylint.interfaces import (
CONTROL_FLOW,
HIGH,
Expand All @@ -57,7 +60,6 @@
# by astroid. Unfortunately this also messes up our explicit checks
# for `abc`
METACLASS_NAME_TRANSFORMS = {"_py_abc": "abc"}
TYPING_TYPE_CHECKS_GUARDS = frozenset({"typing.TYPE_CHECKING", "TYPE_CHECKING"})
BUILTIN_RANGE = "builtins.range"
TYPING_MODULE = "typing"
TYPING_NAMES = frozenset(
Expand Down Expand Up @@ -358,15 +360,6 @@ def _assigned_locally(name_node):
return any(a.name == name_node.name for a in assign_stmts)


def _is_type_checking_import(node: Union[nodes.Import, nodes.ImportFrom]) -> bool:
"""Check if an import node is guarded by a TYPE_CHECKS guard."""
return any(
isinstance(ancestor, nodes.If)
and ancestor.test.as_string() in TYPING_TYPE_CHECKS_GUARDS
for ancestor in node.node_ancestors()
)


def _has_locals_call_after_node(stmt, scope):
skip_nodes = (
nodes.FunctionDef,
Expand Down Expand Up @@ -2729,7 +2722,7 @@ def _check_imports(self, not_consumed):
msg = f"import {imported_name}"
else:
msg = f"{imported_name} imported as {as_name}"
if not _is_type_checking_import(stmt):
if not in_type_checking_block(stmt):
self.add_message("unused-import", args=msg, node=stmt)
elif isinstance(stmt, nodes.ImportFrom) and stmt.modname != FUTURE:
if SPECIAL_OBJ.search(imported_name):
Expand All @@ -2753,7 +2746,7 @@ def _check_imports(self, not_consumed):
msg = f"{imported_name} imported from {stmt.modname}"
else:
msg = f"{imported_name} imported from {stmt.modname} as {as_name}"
if not _is_type_checking_import(stmt):
if not in_type_checking_block(stmt):
self.add_message("unused-import", args=msg, node=stmt)

# Construct string for unused-wildcard-import message
Expand Down
3 changes: 3 additions & 0 deletions pylint/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,3 +193,6 @@ class DeletedMessage(NamedTuple):
"W1513", # deprecated-decorator
]
)


TYPING_TYPE_CHECKS_GUARDS = frozenset({"typing.TYPE_CHECKING", "TYPE_CHECKING"})
13 changes: 9 additions & 4 deletions pylint/extensions/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from pylint.checkers import BaseChecker
from pylint.checkers.utils import (
check_messages,
in_type_checking_block,
is_node_in_type_annotation_context,
is_postponed_evaluation_enabled,
safe_infer,
Expand Down Expand Up @@ -355,8 +356,10 @@ def _check_broken_noreturn(self, node: Union[nodes.Name, nodes.Attribute]) -> No
# NoReturn not part of a Union or Callable type
return

if is_postponed_evaluation_enabled(node) and is_node_in_type_annotation_context(
node
if (
in_type_checking_block(node)
or is_postponed_evaluation_enabled(node)
and is_node_in_type_annotation_context(node)
):
return

Expand Down Expand Up @@ -391,8 +394,10 @@ def _broken_callable_location( # pylint: disable=no-self-use
self, node: Union[nodes.Name, nodes.Attribute]
) -> bool:
"""Check if node would be a broken location for collections.abc.Callable."""
if is_postponed_evaluation_enabled(node) and is_node_in_type_annotation_context(
node
if (
in_type_checking_block(node)
or is_postponed_evaluation_enabled(node)
and is_node_in_type_annotation_context(node)
):
return False

Expand Down
6 changes: 5 additions & 1 deletion tests/functional/ext/typing/typing_broken_callable.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
# pylint: disable=missing-docstring,unsubscriptable-object
import collections.abc
from collections.abc import Callable
from typing import Optional, Union
from typing import TYPE_CHECKING, Optional, Union

Alias1 = Optional[Callable[[int], None]] # [broken-collections-callable]
Alias2 = Union[Callable[[int], None], None] # [broken-collections-callable]
Expand All @@ -17,6 +17,10 @@
Alias5 = list[Callable[..., None]]
Alias6 = Callable[[int], None]

if TYPE_CHECKING:
# ok inside TYPE_CHECKING block
Alias7 = Optional[Callable[[int], None]]


def func1() -> Optional[Callable[[int], None]]: # [broken-collections-callable]
...
Expand Down
4 changes: 2 additions & 2 deletions tests/functional/ext/typing/typing_broken_callable.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
broken-collections-callable:12:18:12:26::'collections.abc.Callable' inside Optional and Union is broken in 3.9.0 / 3.9.1 (use 'typing.Callable' instead):INFERENCE
broken-collections-callable:13:15:13:23::'collections.abc.Callable' inside Optional and Union is broken in 3.9.0 / 3.9.1 (use 'typing.Callable' instead):INFERENCE
broken-collections-callable:21:24:21:32:func1:'collections.abc.Callable' inside Optional and Union is broken in 3.9.0 / 3.9.1 (use 'typing.Callable' instead):INFERENCE
broken-collections-callable:27:21:27:45:func3:'collections.abc.Callable' inside Optional and Union is broken in 3.9.0 / 3.9.1 (use 'typing.Callable' instead):INFERENCE
broken-collections-callable:25:24:25:32:func1:'collections.abc.Callable' inside Optional and Union is broken in 3.9.0 / 3.9.1 (use 'typing.Callable' instead):INFERENCE
broken-collections-callable:31:21:31:45:func3:'collections.abc.Callable' inside Optional and Union is broken in 3.9.0 / 3.9.1 (use 'typing.Callable' instead):INFERENCE
6 changes: 5 additions & 1 deletion tests/functional/ext/typing/typing_broken_noreturn.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"""
# pylint: disable=missing-docstring
import typing
from typing import Callable, NoReturn, Union
from typing import TYPE_CHECKING, Callable, NoReturn, Union

import typing_extensions

Expand All @@ -30,3 +30,7 @@ def func5() -> Union[None, typing_extensions.NoReturn]: # [broken-noreturn]
Alias1 = NoReturn
Alias2 = Callable[..., NoReturn] # [broken-noreturn]
Alias3 = Callable[..., "NoReturn"]

if TYPE_CHECKING:
# ok inside TYPE_CHECKING block
Alias4 = Callable[..., NoReturn]
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from __future__ import annotations

import typing
from typing import Callable, NoReturn, Union
from typing import TYPE_CHECKING, Callable, NoReturn, Union

import typing_extensions

Expand All @@ -35,3 +35,7 @@ def func5() -> Union[None, typing_extensions.NoReturn]:
Alias1 = NoReturn
Alias2 = Callable[..., NoReturn] # [broken-noreturn]
Alias3 = Callable[..., "NoReturn"]

if TYPE_CHECKING:
# ok inside TYPE_CHECKING block
Alias4 = Callable[..., NoReturn]
6 changes: 5 additions & 1 deletion tests/functional/ext/typing/typing_broken_noreturn_py372.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"""
# pylint: disable=missing-docstring
import typing
from typing import Callable, NoReturn, Union
from typing import TYPE_CHECKING, Callable, NoReturn, Union

import typing_extensions

Expand All @@ -32,3 +32,7 @@ def func5() -> Union[None, typing_extensions.NoReturn]:
Alias1 = NoReturn
Alias2 = Callable[..., NoReturn]
Alias3 = Callable[..., "NoReturn"]

if TYPE_CHECKING:
# ok inside TYPE_CHECKING block
Alias4 = Callable[..., NoReturn]

0 comments on commit ef2e176

Please sign in to comment.