Skip to content

Commit

Permalink
Invalid use of @overload v2 (#9127)
Browse files Browse the repository at this point in the history
* Version 2 Checker Duplicated

* Test file missed

* should have been commited before

* Async removed as requested

* final tidy

* Extra File Removed
  • Loading branch information
16234397 authored Oct 11, 2024
1 parent 68c10a4 commit 3db4a75
Show file tree
Hide file tree
Showing 5 changed files with 166 additions and 3 deletions.
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

0 comments on commit 3db4a75

Please sign in to comment.