Skip to content
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

Invalid use of @overload v2 #9127

Merged
merged 9 commits into from
Oct 11, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ In the case of a false positive, use the disable command to remove the pylint er
| do-not-log-raised-errors | Do not log errors at `error` or `warning` level when error is raised in an exception block. | pylint:disable=do-not-log-raised-errors | No Link. |
| do-not-use-legacy-typing | Do not use legacy (<Python 3.8) type hinting comments | pylint:disable=do-not-use-legacy-typing | No Link. |
| do-not-import-asyncio | Do not import asyncio directly. | pylint:disable=do-not-import-asyncio | No Link. |
| invalid-use-of-overload | Do not mix async and synchronous overloads | pylint:disable=invalid-use-of-overload | No Link. | | Add a check for connection_verify hardcoded settings #35355 | | |
| do-not-hardcode-connection-verify | Do not hardcode a boolean value to connection_verify | pylint:disable=do-not-hardcode-connection-verify | No LInk. |
| TODO | custom linter check for invalid use of @overload #3229 | | |
| do-not-log-exceptions | Do not log exceptions in levels other than debug, otherwise it can reveal sensitive information | pylint:disable=do-not-log-exceptions | [link](https://azure.github.io/azure-sdk/python_implementation.html#python-logging-sensitive-info) |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2884,7 +2884,63 @@ def visit_import(self, node):
)


# [Pylint] custom linter check for invalid use of @overload #3229

class InvalidUseOfOverload(BaseChecker):
"""Rule to check that use of the @overload decorator matches the async/sync nature of the underlying function"""

name = "invalid-use-of-overload"
priority = -1
msgs = {
"C4765": (
"Do not mix async and synchronous overloads",
"invalid-use-of-overload",
"Functions and their overloads must be either all async or all synchronous.",
),
}

def visit_classdef(self, node):
"""Check that use of the @overload decorator matches the async/sync nature of the underlying function"""

# Obtain a list of all functions and function names
functions = []
node.body
for item in node.body:
if hasattr(item, 'name'):
functions.append(item)

# Dictionary of lists of all functions by name
overloadedfunctions = {}
for item in functions:
if item.name in overloadedfunctions:
overloadedfunctions[item.name].append(item)
else:
overloadedfunctions[item.name] = [item]


# Loop through the overloaded functions and check they are the same type
for funct in overloadedfunctions.values():
if len(funct) > 1: # only need to check if there is more than 1 function with the same name
function_is_async = None

for item in funct:
if function_is_async is None:
function_is_async = self.is_function_async(item)

else:
if function_is_async != self.is_function_async(item):
self.add_message(
msgid=f"invalid-use-of-overload",
node=item,
confidence=None,
)


def is_function_async(self, node):
try:
str(node.__class__).index("Async")
return True
except:
return False


class DoNotLogExceptions(BaseChecker):
Expand Down Expand Up @@ -3071,9 +3127,9 @@ def register(linter):
linter.register_checker(NoImportTypingFromTypeCheck(linter))
linter.register_checker(DoNotUseLegacyTyping(linter))
linter.register_checker(DoNotLogErrorsEndUpRaising(linter))
linter.register_checker(InvalidUseOfOverload(linter))
linter.register_checker(DoNotLogExceptions(linter))

# [Pylint] custom linter check for invalid use of @overload #3229
# [Pylint] Address Commented out Pylint Custom Plugin Checkers #3228
linter.register_checker(DoNotHardcodeConnectionVerify(linter))
# [Pylint] Investigate pylint rule around missing dependency #3231
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Test file for InvalidUseOfOverload checker

from typing import overload, Union

class testingOverload:
@overload
async def double(a: str):
...

@overload
async def double(a: int):
...

async def double(a: Union[str, int]) -> int:
if isinstance(a, str):
return len(a)*2
return a * 2


@overload
def single(a: str):
...

@overload
def single(a: int):
...

def single(a: Union[str, int]) -> int:
if isinstance(a, str):
return len(a)
return a
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Test file for InvalidUseOfOverload checker - testing what mypy doesn't pick up

from typing import overload, Union

class testingOverload:
@overload
def double(a: str):
...

@overload
def double(a: int):
...

async def double(a: Union[str, int]):
if isinstance(a, str):
return len(a)*2
return a * 2


@overload
async def doubleAgain(a: str) -> int:
...

@overload
def doubleAgain(a: int) -> int:
...

async def doubleAgain(a: Union[str, int]) -> int:
if isinstance(a, str):
return len(a)*2
return a * 2
Original file line number Diff line number Diff line change
Expand Up @@ -3532,8 +3532,52 @@ def test_extra_nested_branches_exception_logged(self, setup):
):
self.checker.visit_try(try_node)


class TestInvalidUseOfOverload(pylint.testutils.CheckerTestCase):
"""Test that use of the @overload decorator matches the async/sync nature of the underlying function"""

# [Pylint] custom linter check for invalid use of @overload #3229
CHECKER_CLASS = checker.InvalidUseOfOverload

def test_valid_use_overload(self):
file = open(
os.path.join(
TEST_FOLDER, "test_files", "invalid_use_of_overload_acceptable.py"
)
)
node = astroid.parse(file.read())
file.close()
with self.assertNoMessages():
self.checker.visit_classdef(node.body[1])


def test_invalid_use_overload(self):
file = open(
os.path.join(
TEST_FOLDER, "test_files", "invalid_use_of_overload_violation.py"
)
)
node = astroid.extract_node(file.read())
file.close()

with self.assertAddsMessages(
pylint.testutils.MessageTest(
msg_id="invalid-use-of-overload",
line=14,
node=node.body[2],
col_offset=4,
end_line=14,
end_col_offset=20,
),
pylint.testutils.MessageTest(
msg_id="invalid-use-of-overload",
line=25,
node=node.body[4],
col_offset=4,
end_line=25,
end_col_offset=19,
),
):
self.checker.visit_classdef(node)


class TestDoNotLogExceptions(pylint.testutils.CheckerTestCase):
Expand Down